Compare commits
50 Commits
v2.2.2.0-1
...
release1.7
| Author | SHA1 | Date | |
|---|---|---|---|
| 268e72318f | |||
| 48b4805d77 | |||
| 7589105c23 | |||
| ef1ae4db86 | |||
| 789196b2b8 | |||
| 9f9e975990 | |||
| d9fb94378c | |||
| 293b11e45d | |||
| 7a68f4e428 | |||
| 4dcbeed7af | |||
| d6266ba72b | |||
| a096240d17 | |||
| dcde900e84 | |||
| d07107ab71 | |||
| 165572fbb8 | |||
| df136769eb | |||
| f1f88f8904 | |||
| 38123579fb | |||
| da08e62017 | |||
| bb1ca99db7 | |||
| 27223b1253 | |||
| 6f8498d0b1 | |||
| 08c968a999 | |||
| f0270bb289 | |||
| 5ae0bb176a | |||
| cd643f0d32 | |||
| 3cf0f27f7b | |||
| 7f5bf63a9b | |||
| ba82d3e423 | |||
| be76296067 | |||
| adcd53c67e | |||
| 4eb5faa1b5 | |||
| a77674145f | |||
| 1dd484cc2d | |||
| 3121008475 | |||
| ce28176fa2 | |||
| 2b9d439b03 | |||
| 29ff0df4b9 | |||
| edb08b56fc | |||
| c783c6754b | |||
| c32e1961c4 | |||
| 67821f0122 | |||
| e2ea7b3297 | |||
| f961082c05 | |||
| 5c00381e26 | |||
| 2912793087 | |||
| 9b5619d741 | |||
| 92382c4181 | |||
| 650e967b30 | |||
| 953a2dfb8a |
@ -1,4 +0,0 @@
|
||||
[report]
|
||||
omit =
|
||||
*/python?.?/*
|
||||
*/site-packages/nose/*
|
||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -1 +0,0 @@
|
||||
CHANGELOG.md merge=union
|
||||
64
.github/ISSUE_TEMPLATE.md
vendored
64
.github/ISSUE_TEMPLATE.md
vendored
@ -1,64 +0,0 @@
|
||||
<!---
|
||||
Please do not report issues/requests related to Ansible modules here !!
|
||||
|
||||
Report them to the appropriate modules-core or modules-extras project:
|
||||
- https://github.com/ansible/ansible-modules-core/issues
|
||||
- https://github.com/ansible/ansible-modules-extras/issues
|
||||
|
||||
Also verify first that your issue/request is not already reported in GitHub
|
||||
-->
|
||||
|
||||
|
||||
##### ISSUE TYPE
|
||||
<!--- Pick one below and delete the rest: -->
|
||||
- Bug Report
|
||||
- Feature Idea
|
||||
- Documentation Report
|
||||
|
||||
##### COMPONENT NAME
|
||||
<!--- Name of the 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.
|
||||
-->
|
||||
|
||||
##### SUMMARY
|
||||
<!--- Explain the problem briefly -->
|
||||
|
||||
##### STEPS TO REPRODUCE
|
||||
<!---
|
||||
For bugs, show exactly how to reproduce the problem.
|
||||
For new features, show how the feature would be used.
|
||||
-->
|
||||
|
||||
<!--- Paste example playbooks or commands between quotes below -->
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
<!--- 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 -->
|
||||
```
|
||||
|
||||
```
|
||||
29
.github/PULL_REQUEST_TEMPLATE.md
vendored
29
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,29 +0,0 @@
|
||||
##### 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 plugin/module/task -->
|
||||
|
||||
##### ANSIBLE VERSION
|
||||
<!--- Paste verbatim output from “ansible --version” between quotes below -->
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
##### 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.
|
||||
-->
|
||||
|
||||
<!-- Paste verbatim command output below, e.g. before and after your change -->
|
||||
```
|
||||
|
||||
```
|
||||
15
.gitignore
vendored
15
.gitignore
vendored
@ -19,19 +19,12 @@ rpm-build
|
||||
# Mac OS X stuff...
|
||||
.DS_Store
|
||||
# manpage build stuff...
|
||||
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...
|
||||
docsite/rst/modules_by_category.rst
|
||||
docsite/rst/playbooks_directives.rst
|
||||
docsite/rst/list_of_*.rst
|
||||
docsite/rst/*_module.rst
|
||||
docsite/*.html
|
||||
@ -40,20 +33,15 @@ docsite/_static/*.png
|
||||
docsite/_static/websupport.js
|
||||
docsite/searchindex.js
|
||||
docsite/htmlout
|
||||
docs-api/rst/
|
||||
docs-api/_build/
|
||||
# deb building stuff...
|
||||
/debian/
|
||||
debian/
|
||||
deb-build
|
||||
# Vim swap files
|
||||
*.swp
|
||||
*.swo
|
||||
credentials.yml
|
||||
# test output
|
||||
*.retry
|
||||
*.out
|
||||
.coverage
|
||||
.tox
|
||||
results.xml
|
||||
coverage.xml
|
||||
/test/units/cover-html
|
||||
@ -63,4 +51,3 @@ venv
|
||||
Vagrantfile
|
||||
.vagrant
|
||||
ansible.egg-info/
|
||||
/shippable/
|
||||
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -1,6 +0,0 @@
|
||||
[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
|
||||
35
.mailmap
35
.mailmap
@ -1,35 +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>
|
||||
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>
|
||||
|
||||
|
||||
22
.yamllint
22
.yamllint
@ -1,22 +0,0 @@
|
||||
extends: default
|
||||
|
||||
# Disable all cosmetic rules
|
||||
# (see https://github.com/ansible/ansible/pull/15470#issuecomment-214437876)
|
||||
# Only keep 'key-duplicates' and 'new-lines: {type: unix}' checks enabled.
|
||||
|
||||
rules:
|
||||
braces: disable
|
||||
brackets: disable
|
||||
colons: disable
|
||||
commas: disable
|
||||
comments: disable
|
||||
comments-indentation: disable
|
||||
document-start: disable
|
||||
empty-lines: disable
|
||||
hyphens: disable
|
||||
indentation: disable
|
||||
key-duplicates: enable
|
||||
line-length: disable
|
||||
new-line-at-end-of-file: disable
|
||||
new-lines: {type: unix}
|
||||
trailing-spaces: disable
|
||||
1837
CHANGELOG.md
1837
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@ -277,8 +277,7 @@ To test if something is a string, consider that it may be unicode.
|
||||
if type(x) == str:
|
||||
|
||||
# yes
|
||||
from ansible.compat.six import string_types
|
||||
if isinstance(x, string_types):
|
||||
if isinstance(x, basestring):
|
||||
|
||||
Cleverness
|
||||
==========
|
||||
|
||||
231
CONTRIBUTING.md
231
CONTRIBUTING.md
@ -1,29 +1,224 @@
|
||||
# WELCOME TO ANSIBLE GITHUB
|
||||
Ansible Community Information
|
||||
==============================
|
||||
|
||||
Hi! Nice to see you here!
|
||||
The purpose of the Ansible community is to unite developers, system administrators, operations, and
|
||||
IT managers to share and build great automation solutions. This document contains all sorts of
|
||||
information about how to contribute and interact with Ansible. Welcome!
|
||||
|
||||
Ways to Interact
|
||||
================
|
||||
|
||||
There are a lot of ways to join and be a part of the Ansible community, such as:
|
||||
|
||||
Sharing Ansible with Others
|
||||
---------------------------
|
||||
|
||||
You can help share Ansible with others by telling friends and colleagues, writing a blog post,
|
||||
or presenting at user groups (like DevOps groups or the local LUG or BUG). You are also
|
||||
welcome to share slides on speakerdeck, sign up for a free account and tag it “Ansible”. On Twitter,
|
||||
you can also share things with #ansible and may wish
|
||||
to follow [@Ansible](https://twitter.com/ansible).
|
||||
|
||||
Sharing Content and Tips
|
||||
------------------------
|
||||
|
||||
Join the [Ansible project mailing list](https://groups.google.com/forum/#!forum/ansible-project) and you
|
||||
can share playbooks you may have written and other interesting implementation stories. Put your Ansible
|
||||
content up on places like github to share with others.
|
||||
|
||||
Sharing A Feature Idea
|
||||
----------------------
|
||||
|
||||
Ideas are very welcome and the best place to share them is the [Ansible project mailing list](https://groups.google.com/forum/#!forum/ansible-project) ([Subscribe](https://groups.google.com/forum/#!forum/ansible-project/join)) or #ansible on irc.freenode.net.
|
||||
|
||||
While you can file a feature request on GitHub, pull requests are a much better way to get your feature added than submitting a feature request. Open source is all about itch scratching, and it's less likely that someone else will have the same itches as yourself. We keep code reasonably simple on purpose so it's easy to dive in and make additions, but be sure to read the "Contributing Code" section below too -- as it doesn't hurt to have a discussion about a feature first -- we're inclined to have preferences about how incoming features might be implemented, and that can save confusion later.
|
||||
|
||||
Helping with Documentation
|
||||
--------------------------
|
||||
|
||||
Ansible documentation is a community project too! If you would like to help with the
|
||||
documentation, whether correcting a typo or improving a section, or maybe even
|
||||
documenting a new feature, submit a github pull request to the code that
|
||||
lives in the “docsite/rst” subdirectory of the project. Docs are in restructured text
|
||||
format. If you aren’t comfortable with restructured text, you can also open a ticket on
|
||||
github about any errors you spot or sections you would like to see added. For more information
|
||||
on creating pull requests, please refer to the
|
||||
[github help guide](https://help.github.com/articles/using-pull-requests).
|
||||
|
||||
|
||||
## QUESTIONS ?
|
||||
Contributing Code (Features or Bugfixes)
|
||||
----------------------------------------
|
||||
|
||||
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 Ansible project keeps it’s source on github at
|
||||
[github.com/ansible/ansible](http://github.com/ansible/ansible)
|
||||
and takes contributions through
|
||||
[github pull requests](https://help.github.com/articles/using-pull-requests).
|
||||
|
||||
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.
|
||||
It is usually a good idea to join the ansible-devel list to discuss any large features prior to submission, and this especially helps in avoiding duplicate work or efforts where we decide, upon seeing a pull request for the first time, that revisions are needed. (This is not usually needed for module development)
|
||||
|
||||
Note that we do keep Ansible to a particular aesthetic, so if you are unclear about whether a feature
|
||||
is a good fit or not, having the discussion on the development list is often a lot easier than having
|
||||
to modify a pull request later.
|
||||
|
||||
When submitting patches, be sure to run the unit tests first “make tests” and always use
|
||||
“git rebase” vs “git merge” (aliasing git pull to git pull --rebase is a great idea) to
|
||||
avoid merge commits in your submissions. There are also integration tests that can be run in the "tests/integration" directory.
|
||||
|
||||
In order to keep the history clean and better audit incoming code, we will require resubmission of pull requests that contain merge commits. Use "git pull --rebase" vs "git pull" and "git rebase" vs "git merge". Also be sure to use topic branches to keep your additions on different branches, such that they won't pick up stray commits later.
|
||||
|
||||
We’ll then review your contributions and engage with you about questions and so on.
|
||||
|
||||
As we have a very large and active community, so it may take awhile to get your contributions
|
||||
in! See the notes about priorities in a later section for understanding our work queue.
|
||||
|
||||
Patches should be made against the 'devel' branch.
|
||||
|
||||
Contributions can be for new features like modules, or to fix bugs you or others have found. If you
|
||||
are interested in writing new modules to be included in the core Ansible distribution, please refer
|
||||
to the [Module Developers documentation on our website](http://docs.ansible.com/developing_modules.html).
|
||||
|
||||
Ansible's aesthetic encourages simple, readable code and consistent, conservatively extending,
|
||||
backwards-compatible improvements. Code developed for Ansible needs to support Python 2.6+,
|
||||
while code in modules must run under Python 2.4 or higher. Please also use a 4-space indent
|
||||
and no tabs.
|
||||
|
||||
Tip: To easily run from a checkout, source "./hacking/env-setup" and that's it -- no install
|
||||
required. You're now live!
|
||||
|
||||
|
||||
## CONTRIBUTING ?
|
||||
Reporting 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.
|
||||
Ansible practices responsible disclosure - if this is a security related bug, email security@ansible.com instead of filing a ticket or posting to the Google Group and you will receive a prompt response.
|
||||
|
||||
Bugs should be reported to [github.com/ansible/ansible](http://github.com/ansible/ansible) after
|
||||
signing up for a free github account. Before reporting a bug, please use the bug/issue search
|
||||
to see if the issue has already been reported.
|
||||
|
||||
When filing a bug, please use the [issue template](https://raw2.github.com/ansible/ansible/devel/examples/issues/ISSUE_TEMPLATE.md) to provide all relevant information.
|
||||
|
||||
Do not use the issue tracker for "how do I do this" type questions. These are great candidates
|
||||
for IRC or the mailing list instead where things are likely to be more of a discussion.
|
||||
|
||||
To be respectful of reviewers time and allow us to help everyone efficiently, please
|
||||
provide minimal well-reduced and well-commented examples versus sharing your entire production
|
||||
playbook. Include playbook snippets and output where possible.
|
||||
|
||||
Content in the GitHub bug tracker can be indented four spaces to preserve formatting.
|
||||
For multiple-file content, we encourage use of gist.github.com. Online pastebin content can expire.
|
||||
|
||||
If you are not sure if something is a bug yet, you are welcome to ask about something on
|
||||
the mailing list or IRC first. As we are a very high volume project, if you determine that
|
||||
you do have a bug, please be sure to open the issue yourself to ensure we have a record of
|
||||
it. Don’t rely on someone else in the community to file the bug report for you.
|
||||
|
||||
It may take some time to get to your report, see "A Note About Priorities" below.
|
||||
|
||||
A Note About Priorities
|
||||
=======================
|
||||
|
||||
Ansible was one of the top 5 projects with the most OSS contributors on GitHub in 2013, and well over
|
||||
600 people have added code to the project. As a result, we have a LOT of incoming activity to process.
|
||||
|
||||
In the interest of transparency, we're telling you how we do this.
|
||||
|
||||
In our bug tracker you'll notice some labels - P1, P2, P3, P4, and P5. These are our internal
|
||||
priority orders that we use to sort tickets.
|
||||
|
||||
With some exceptions for easy merges (like documentation typos for instance),
|
||||
we're going to spend most of our time working on P1 and P2 items first, including pull requests.
|
||||
These usually relate to important
|
||||
bugs or features affecting large segments of the userbase. So if you see something categorized
|
||||
"P3 or P4", and it's not appearing to get a lot of immediate attention, this is why.
|
||||
|
||||
These labels don't really have definition - they are a simple ordering. However something
|
||||
affecting a major module (yum, apt, etc) is likely to be prioritized higher than a module
|
||||
affecting a smaller number of users.
|
||||
|
||||
Since we place a strong emphasis on testing and code review, it may take a few months for a minor feature to get merged.
|
||||
|
||||
Don't worry though -- we'll also take periodic sweeps through the lower priority queues and give
|
||||
them some attention as well, particularly in the area of new module changes. So it doesn't neccessarily
|
||||
mean that we'll be exhausting all of the higher-priority queues before getting to your ticket.
|
||||
|
||||
Release Numbering
|
||||
=================
|
||||
|
||||
Releases ending in ".0" are major releases and this is where all new features land. Releases ending
|
||||
in another integer, like "0.X.1" and "0.X.2" are dot releases, and these are only going to contain
|
||||
bugfixes. Typically we don't do dot releases for minor releases, but may occasionally decide to cut
|
||||
dot releases containing a large number of smaller fixes if it's still a fairly long time before
|
||||
the next release comes out.
|
||||
|
||||
Online Resources
|
||||
================
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
The main ansible documentation can be found at [docs.ansible.com](http://docs.ansible.com).
|
||||
As mentioned above this is an open source project, so we accept contributions to the documentation.
|
||||
You can also find some best practices examples that we recommend reading at
|
||||
[ansible-examples](http://github.com/ansible/ansible-examples).
|
||||
|
||||
Mailing lists
|
||||
-------------
|
||||
|
||||
Ansible has several mailing lists. Your first post to the mailing list will be
|
||||
moderated (to reduce spam), so please allow a day or less for your first post.
|
||||
|
||||
[ansible-announce](https://groups.google.com/forum/#!forum/ansible-announce) is for release
|
||||
announcements and major news. It is a low traffic read-only list and you should only get a few
|
||||
emails a month.
|
||||
|
||||
[ansible-project](https://groups.google.com/forum/#!forum/ansible-project) is the main list, and is
|
||||
used for sharing cool projects you may have built, talking about Ansible ideas, and for users to ask
|
||||
questions or to help other users.
|
||||
|
||||
[ansible-devel](https://groups.google.com/forum/#!forum/ansible-devel) is a technical list for
|
||||
developers working on Ansible and Ansible modules. Join here to discuss how to build modules,
|
||||
prospective feature implementations, or technical challenges.
|
||||
|
||||
To subscribe to a group from a non-google account, you can email the subscription address, for
|
||||
example ansible-devel+subscribe@googlegroups.com.
|
||||
|
||||
IRC
|
||||
---
|
||||
|
||||
Ansible has a general purpose IRC channel available at #ansible on irc.freenode.net.
|
||||
Use this channel for all types of conversations, including sharing tips, coordinating
|
||||
development work, or getting help from other users.
|
||||
|
||||
Miscellaneous Information
|
||||
=========================
|
||||
|
||||
Staff
|
||||
-----
|
||||
|
||||
Ansible, Inc is a company supporting Ansible and building additional solutions based on
|
||||
Ansible. We also do services and support for those that are interested. Our most
|
||||
important task however is enabling all the great things that happen in the Ansible
|
||||
community, including organizing software releases of Ansible. For more information about
|
||||
any of these things, contact info@ansible.com
|
||||
|
||||
On IRC, you can find us as mdehaan, jimi_c, Tybstar, and others. On the mailing list,
|
||||
we post with an @ansible.com address.
|
||||
|
||||
Community Code of Conduct
|
||||
-------------------------
|
||||
|
||||
Ansible’s community welcomes users of all types, backgrounds, and skill levels. Please
|
||||
treat others as you expect to be treated, keep discussions positive, and avoid discrimination, profanity, allegations of Cthulhu worship, or engaging in controversial debates (except vi vs emacs is cool).
|
||||
|
||||
Posts to mailing lists should remain focused around Ansible and IT automation. Abuse of these community guidelines will not be tolerated and may result in banning from community resources.
|
||||
|
||||
Contributors License Agreement
|
||||
------------------------------
|
||||
|
||||
By contributing you agree that these contributions are your own (or approved by your employer)
|
||||
and you grant a full, complete, irrevocable
|
||||
copyright license to all users and developers of the project, present and future, pursuant
|
||||
to the license of the project.
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
Thanks!
|
||||
|
||||
21
MANIFEST.in
21
MANIFEST.in
@ -1,24 +1,11 @@
|
||||
prune v2
|
||||
prune docsite
|
||||
prune ticket_stubs
|
||||
prune packaging
|
||||
prune test
|
||||
prune hacking
|
||||
include README.md COPYING
|
||||
include README.md packaging/rpm/ansible.spec COPYING
|
||||
include examples/hosts
|
||||
include examples/ansible.cfg
|
||||
graft examples/playbooks
|
||||
include packaging/distutils/setup.py
|
||||
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 library *
|
||||
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
|
||||
|
||||
74
Makefile
74
Makefile
@ -10,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 test/README.md for requirements)
|
||||
# make tests ---------------- run the tests
|
||||
# make pyflakes, make pep8 -- source code checks
|
||||
|
||||
########################################################
|
||||
@ -24,8 +24,8 @@ OS = $(shell uname -s)
|
||||
# directory of the target file ($@), kinda like `dirname`.
|
||||
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
|
||||
@ -34,20 +34,14 @@ PYTHON=python
|
||||
SITELIB = $(shell $(PYTHON) -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")
|
||||
|
||||
# VERSION file provides one place to update the software version
|
||||
VERSION := $(shell cat VERSION | cut -f1 -d' ')
|
||||
RELEASE := $(shell cat VERSION | cut -f2 -d' ')
|
||||
VERSION := $(shell cat VERSION)
|
||||
|
||||
# Get the branch information from git
|
||||
ifneq ($(shell which git),)
|
||||
GIT_DATE := $(shell git log -n 1 --format="%ai")
|
||||
GIT_HASH := $(shell git log -n 1 --format="%h")
|
||||
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD | sed 's/[-_.\/]//g')
|
||||
GITINFO = .$(GIT_HASH).$(GIT_BRANCH)
|
||||
else
|
||||
GITINFO = ""
|
||||
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)
|
||||
else
|
||||
DATE := $(shell date --utc --date="$(GIT_DATE)" +%Y%m%d%H%M)
|
||||
@ -58,16 +52,15 @@ DEBUILD_BIN ?= debuild
|
||||
DEBUILD_OPTS = --source-option="-I"
|
||||
DPUT_BIN ?= dput
|
||||
DPUT_OPTS ?=
|
||||
DEB_DATE := $(shell LC_TIME=C date +"%a, %d %b %Y %T %z")
|
||||
ifeq ($(OFFICIAL),yes)
|
||||
DEB_RELEASE = $(RELEASE)ppa
|
||||
DEB_RELEASE = 1ppa
|
||||
# Sign OFFICIAL builds using 'DEBSIGN_KEYID'
|
||||
# DEBSIGN_KEYID is required when signing
|
||||
ifneq ($(DEBSIGN_KEYID),)
|
||||
DEBUILD_OPTS += -k$(DEBSIGN_KEYID)
|
||||
endif
|
||||
else
|
||||
DEB_RELEASE = 100.git$(DATE)$(GITINFO)
|
||||
DEB_RELEASE = 0.git$(DATE)
|
||||
# Do not sign unofficial builds
|
||||
DEBUILD_OPTS += -uc -us
|
||||
DPUT_OPTS += -u
|
||||
@ -77,19 +70,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)
|
||||
RPMRELEASE = 1
|
||||
ifneq ($(OFFICIAL),yes)
|
||||
RPMRELEASE = 100.git$(DATE)$(GITINFO)
|
||||
RPMRELEASE = 0.git$(DATE)
|
||||
endif
|
||||
RPMNVR = "$(NAME)-$(VERSION)-$(RPMRELEASE)$(RPMDIST)"
|
||||
|
||||
@ -99,20 +86,12 @@ MOCK_CFG ?=
|
||||
|
||||
NOSETESTS ?= nosetests
|
||||
|
||||
NOSETESTS3 ?= nosetests-3.5
|
||||
|
||||
########################################################
|
||||
|
||||
all: clean python
|
||||
|
||||
tests:
|
||||
PYTHONPATH=./lib $(NOSETESTS) -d -w test/units -v --with-coverage --cover-package=ansible --cover-branches --cover-erase
|
||||
|
||||
tests-py3:
|
||||
PYTHONPATH=./lib $(NOSETESTS3) -d -w test/units -v --with-coverage --cover-package=ansible --cover-branches --cover-erase
|
||||
|
||||
integration:
|
||||
test/utils/shippable/integration.sh
|
||||
PYTHONPATH=./lib ANSIBLE_LIBRARY=./library $(NOSETESTS) -d -w test/units -v
|
||||
|
||||
authors:
|
||||
sh hacking/authors.sh
|
||||
@ -135,7 +114,7 @@ pep8:
|
||||
@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/
|
||||
-pep8 -r --ignore=E501,E221,W291,W391,E302,E251,E203,W293,E231,E303,E201,E225,E261,E241 --filename "*" library/
|
||||
|
||||
pyflakes:
|
||||
pyflakes lib/ansible/*.py lib/ansible/*/*.py bin/*
|
||||
@ -144,11 +123,10 @@ clean:
|
||||
@echo "Cleaning up distutils stuff"
|
||||
rm -rf build
|
||||
rm -rf dist
|
||||
rm -rf lib/ansible.egg-info/
|
||||
@echo "Cleaning up byte compiled python stuff"
|
||||
find . -type f -regex ".*\.py[co]$$" -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 "*~" -or -name "#*" \) -delete
|
||||
find . -type f \( -name "*.swp" \) -delete
|
||||
@echo "Cleaning up manpage stuff"
|
||||
find ./docs/man -type f -name "*.xml" -delete
|
||||
@ -165,7 +143,6 @@ clean:
|
||||
rm -rf docs/js
|
||||
@echo "Cleaning up authors file"
|
||||
rm -f AUTHORS.TXT
|
||||
find . -type f -name '*.pyc' -delete
|
||||
|
||||
python:
|
||||
$(PYTHON) setup.py build
|
||||
@ -176,9 +153,6 @@ install:
|
||||
sdist: clean docs
|
||||
$(PYTHON) setup.py sdist
|
||||
|
||||
sdist_upload: clean docs
|
||||
$(PYTHON) setup.py sdist upload 2>&1 |tee upload.log
|
||||
|
||||
rpmcommon: $(MANPAGES) sdist
|
||||
@mkdir -p rpm-build
|
||||
@cp dist/*.gz rpm-build/
|
||||
@ -233,26 +207,10 @@ debian: sdist
|
||||
mkdir -p deb-build/$${DIST} ; \
|
||||
tar -C deb-build/$${DIST} -xvf dist/$(NAME)-$(VERSION).tar.gz ; \
|
||||
cp -a packaging/debian deb-build/$${DIST}/$(NAME)-$(VERSION)/ ; \
|
||||
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 ; \
|
||||
sed -ie "s#^$(NAME) (\([^)]*\)) \([^;]*\);#ansible (\1-$(DEB_RELEASE)~$${DIST}) $${DIST};#" deb-build/$${DIST}/$(NAME)-$(VERSION)/debian/changelog ; \
|
||||
done
|
||||
|
||||
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.
|
||||
local_deb: debian
|
||||
deb: debian
|
||||
@for DIST in $(DEB_DIST) ; do \
|
||||
(cd deb-build/$${DIST}/$(NAME)-$(VERSION)/ && $(DEBUILD) -b) ; \
|
||||
done
|
||||
@ -286,9 +244,7 @@ deb-src-upload: deb-src
|
||||
|
||||
# for arch or gentoo, read instructions in the appropriate 'packaging' subdirectory directory
|
||||
|
||||
webdocs:
|
||||
webdocs: $(MANPAGES)
|
||||
(cd docsite/; make docs)
|
||||
|
||||
docs: $(MANPAGES)
|
||||
|
||||
alldocs: docs webdocs
|
||||
|
||||
46
README.md
46
README.md
@ -1,29 +1,25 @@
|
||||
[](https://pypi.python.org/pypi/ansible)
|
||||
[](https://app.shippable.com/projects/573f79d02a8192902e20e34b)
|
||||
[](http://badge.fury.io/py/ansible) [](https://pypi.python.org/pypi/ansible)
|
||||
|
||||
|
||||
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.
|
||||
Ansible is a radically simple configuration-management, application deployment, task-execution, and multinode orchestration engine.
|
||||
|
||||
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.
|
||||
|
||||
You can find instructions [here](https://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](https://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`.
|
||||
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](http://docs.ansible.com/intro_getting_started.html) for a variety of platforms. If you want a tarball of the last release, go to [releases.ansible.com](http://releases.ansible.com/ansible) and you can also install with pip.
|
||||
|
||||
Design Principles
|
||||
=================
|
||||
|
||||
* Have a dead simple setup process and a minimal learning curve
|
||||
* Manage machines very quickly and in parallel
|
||||
* Avoid custom-agents and additional open ports, be agentless by leveraging the existing SSH daemon
|
||||
* Describe infrastructure in a language that is both machine and human friendly
|
||||
* Be super fast & parallel by default
|
||||
* Require no server or client daemons; use existing SSHd
|
||||
* Use a language that is both machine and human friendly
|
||||
* Focus on security and easy auditability/review/rewriting of content
|
||||
* Manage new remote machines instantly, without bootstrapping any software
|
||||
* Manage remote machines instantly, without bootstrapping
|
||||
* Allow module development in any dynamic language, not just Python
|
||||
* Be usable as non-root
|
||||
* Be the easiest IT automation system to use, ever.
|
||||
@ -31,27 +27,23 @@ Design Principles
|
||||
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.
|
||||
* 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
|
||||
* Read [Contributing.md](https://github.com/ansible/ansible/blob/devel/CONTRIBUTING.md) 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.
|
||||
* irc.freenode.net: #ansible
|
||||
|
||||
Branch Info
|
||||
===========
|
||||
|
||||
* Releases are named after Led Zeppelin songs. (Releases prior to 2.0 were named after Van Halen songs.)
|
||||
* Releases are named after Van Halen songs.
|
||||
* The devel branch corresponds to the release actively under development.
|
||||
* 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.
|
||||
* Various release-X.Y branches exist for previous releases
|
||||
* We'd love to have your contributions, read "CONTRIBUTING.md" for process notes.
|
||||
|
||||
Authors
|
||||
=======
|
||||
Author
|
||||
======
|
||||
|
||||
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)
|
||||
Ansible was created by Michael DeHaan (michael@ansible.com) and has contributions from over
|
||||
700 users (and growing). Thanks everyone!
|
||||
|
||||
[Ansible, Inc](http://ansible.com)
|
||||
|
||||
|
||||
127
RELEASES.txt
127
RELEASES.txt
@ -1,80 +1,55 @@
|
||||
Ansible Releases at a Glance
|
||||
============================
|
||||
|
||||
VERSION RELEASE CODE NAME
|
||||
++++++++++++++++++++++++++++++
|
||||
Active Development
|
||||
++++++++++++++++++
|
||||
|
||||
1.8 "You Really Got Me" ---- FALL 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.4.0 TBD "Dancing Days"
|
||||
2.3.0 TBD "Ramble On"
|
||||
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.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"
|
||||
|
||||
25
ROADMAP.rst
25
ROADMAP.rst
@ -1,25 +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:
|
||||
|
||||
- Ansible's google-group: ansible-devel
|
||||
- Ansible Fest conferences.
|
||||
- IRC freenode channel: #ansible-devel (this one may have things lost in lots of conversation, so a caution).
|
||||
|
||||
=======================================
|
||||
Current 2.2 Roadmap is open for comment
|
||||
=======================================
|
||||
We are currently trying to finalize the 2.2 Roadmap, targeted for September delivery. We are seeking community feedback for 2.2. That document is here:
|
||||
|
||||
`2.2 Roadmap Document <docsite/rst/roadmap/ROADMAP_2_2.rst>`_
|
||||
|
||||
|
||||
**Old Roadmaps are found here:**
|
||||
- `2.1 Roadmap Document <docsite/rst/roadmap/ROADMAP_2_1.rst>`_
|
||||
File diff suppressed because it is too large
Load Diff
291
bin/ansible
291
bin/ansible
@ -18,122 +18,205 @@
|
||||
# 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:
|
||||
# Use pkg_resources to find the correct versions of libraries and set
|
||||
# sys.path appropriately when there are multiversion installs. But we
|
||||
# have code that better expresses the errors in the places where the code
|
||||
# is actually used (the deps are optional for many code paths) so we don't
|
||||
# want to fail here.
|
||||
pass
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
# for debug
|
||||
from multiprocessing import Lock
|
||||
|
||||
from ansible.runner import Runner
|
||||
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 import utils
|
||||
from ansible import errors
|
||||
from ansible import callbacks
|
||||
from ansible import inventory
|
||||
########################################################
|
||||
|
||||
class Cli(object):
|
||||
''' code behind bin/ansible '''
|
||||
|
||||
# ----------------------------------------------
|
||||
|
||||
def __init__(self):
|
||||
self.stats = callbacks.AggregateStats()
|
||||
self.callbacks = callbacks.CliRunnerCallbacks()
|
||||
|
||||
# ----------------------------------------------
|
||||
|
||||
def parse(self):
|
||||
''' create an options parser for bin/ansible '''
|
||||
|
||||
parser = utils.base_parser(
|
||||
constants=C,
|
||||
runas_opts=True,
|
||||
subset_opts=True,
|
||||
async_opts=True,
|
||||
output_opts=True,
|
||||
connect_opts=True,
|
||||
check_opts=True,
|
||||
diff_opts=False,
|
||||
usage='%prog <host-pattern> [options]'
|
||||
)
|
||||
|
||||
parser.add_option('-a', '--args', dest='module_args',
|
||||
help="module arguments", default=C.DEFAULT_MODULE_ARGS)
|
||||
parser.add_option('-m', '--module-name', dest='module_name',
|
||||
help="module name to execute (default=%s)" % C.DEFAULT_MODULE_NAME,
|
||||
default=C.DEFAULT_MODULE_NAME)
|
||||
|
||||
options, args = parser.parse_args()
|
||||
self.callbacks.options = options
|
||||
|
||||
if len(args) == 0 or len(args) > 1:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
# su and sudo command line arguments need to be mutually exclusive
|
||||
if (options.su or options.su_user or options.ask_su_pass) and \
|
||||
(options.sudo or options.sudo_user or options.ask_sudo_pass):
|
||||
parser.error("Sudo arguments ('--sudo', '--sudo-user', and '--ask-sudo-pass') "
|
||||
"and su arguments ('-su', '--su-user', and '--ask-su-pass') are "
|
||||
"mutually exclusive")
|
||||
|
||||
if (options.ask_vault_pass and options.vault_password_file):
|
||||
parser.error("--ask-vault-pass and --vault-password-file are mutually exclusive")
|
||||
|
||||
return (options, args)
|
||||
|
||||
# ----------------------------------------------
|
||||
|
||||
def run(self, options, args):
|
||||
''' use Runner lib to do SSH things '''
|
||||
|
||||
pattern = args[0]
|
||||
|
||||
"""
|
||||
inventory_manager = inventory.Inventory(options.inventory)
|
||||
if options.subset:
|
||||
inventory_manager.subset(options.subset)
|
||||
hosts = inventory_manager.list_hosts(pattern)
|
||||
if len(hosts) == 0:
|
||||
callbacks.display("No hosts matched", stderr=True)
|
||||
sys.exit(0)
|
||||
|
||||
if options.listhosts:
|
||||
for host in hosts:
|
||||
callbacks.display(' %s' % host)
|
||||
sys.exit(0)
|
||||
|
||||
if ((options.module_name == 'command' or options.module_name == 'shell')
|
||||
and not options.module_args):
|
||||
callbacks.display("No argument passed to %s module" % options.module_name, color='red', stderr=True)
|
||||
sys.exit(1)
|
||||
"""
|
||||
|
||||
sshpass = None
|
||||
sudopass = None
|
||||
su_pass = None
|
||||
vault_pass = None
|
||||
|
||||
options.ask_pass = options.ask_pass or C.DEFAULT_ASK_PASS
|
||||
# Never ask for an SSH password when we run with local connection
|
||||
if options.connection == "local":
|
||||
options.ask_pass = False
|
||||
options.ask_sudo_pass = options.ask_sudo_pass or C.DEFAULT_ASK_SUDO_PASS
|
||||
options.ask_su_pass = options.ask_su_pass or C.DEFAULT_ASK_SU_PASS
|
||||
options.ask_vault_pass = options.ask_vault_pass or C.DEFAULT_ASK_VAULT_PASS
|
||||
|
||||
(sshpass, sudopass, su_pass, vault_pass) = utils.ask_passwords(ask_pass=options.ask_pass, ask_sudo_pass=options.ask_sudo_pass, ask_su_pass=options.ask_su_pass, ask_vault_pass=options.ask_vault_pass)
|
||||
|
||||
# read vault_pass from a file
|
||||
if not options.ask_vault_pass and options.vault_password_file:
|
||||
vault_pass = utils.read_vault_file(options.vault_password_file)
|
||||
|
||||
inventory_manager = inventory.Inventory(options.inventory, vault_password=vault_pass)
|
||||
if options.subset:
|
||||
inventory_manager.subset(options.subset)
|
||||
hosts = inventory_manager.list_hosts(pattern)
|
||||
if len(hosts) == 0:
|
||||
callbacks.display("No hosts matched", stderr=True)
|
||||
sys.exit(0)
|
||||
|
||||
if options.listhosts:
|
||||
for host in hosts:
|
||||
callbacks.display(' %s' % host)
|
||||
sys.exit(0)
|
||||
|
||||
if ((options.module_name == 'command' or options.module_name == 'shell')
|
||||
and not options.module_args):
|
||||
callbacks.display("No argument passed to %s module" % options.module_name, color='red', stderr=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
########################################
|
||||
### OUTPUT OF LAST RESORT ###
|
||||
class LastResort(object):
|
||||
def display(self, msg):
|
||||
print(msg, file=sys.stderr)
|
||||
if options.su_user or options.ask_su_pass:
|
||||
options.su = True
|
||||
options.sudo_user = options.sudo_user or C.DEFAULT_SUDO_USER
|
||||
options.su_user = options.su_user or C.DEFAULT_SU_USER
|
||||
if options.tree:
|
||||
utils.prepare_writeable_dir(options.tree)
|
||||
|
||||
def error(self, msg, wrap_text=None):
|
||||
print(msg, file=sys.stderr)
|
||||
runner = Runner(
|
||||
module_name=options.module_name,
|
||||
module_path=options.module_path,
|
||||
module_args=options.module_args,
|
||||
remote_user=options.remote_user,
|
||||
remote_pass=sshpass,
|
||||
inventory=inventory_manager,
|
||||
timeout=options.timeout,
|
||||
private_key_file=options.private_key_file,
|
||||
forks=options.forks,
|
||||
pattern=pattern,
|
||||
callbacks=self.callbacks,
|
||||
sudo=options.sudo,
|
||||
sudo_pass=sudopass,
|
||||
sudo_user=options.sudo_user,
|
||||
transport=options.connection,
|
||||
subset=options.subset,
|
||||
check=options.check,
|
||||
diff=options.check,
|
||||
su=options.su,
|
||||
su_pass=su_pass,
|
||||
su_user=options.su_user,
|
||||
vault_pass=vault_pass
|
||||
)
|
||||
|
||||
if options.seconds:
|
||||
callbacks.display("background launch...\n\n", color='cyan')
|
||||
results, poller = runner.run_async(options.seconds)
|
||||
results = self.poll_while_needed(poller, options)
|
||||
else:
|
||||
results = runner.run()
|
||||
|
||||
return (runner, results)
|
||||
|
||||
# ----------------------------------------------
|
||||
|
||||
def poll_while_needed(self, poller, options):
|
||||
''' summarize results from Runner '''
|
||||
|
||||
# BACKGROUND POLL LOGIC when -B and -P are specified
|
||||
if options.seconds and options.poll_interval > 0:
|
||||
poller.wait(options.seconds, options.poll_interval)
|
||||
|
||||
return poller.results
|
||||
|
||||
|
||||
########################################
|
||||
########################################################
|
||||
|
||||
if __name__ == '__main__':
|
||||
callbacks.display("", log_only=True)
|
||||
callbacks.display(" ".join(sys.argv), log_only=True)
|
||||
callbacks.display("", log_only=True)
|
||||
|
||||
display = LastResort()
|
||||
cli = None
|
||||
me = os.path.basename(sys.argv[0])
|
||||
|
||||
cli = Cli()
|
||||
(options, args) = cli.parse()
|
||||
try:
|
||||
display = Display()
|
||||
display.debug("starting run")
|
||||
(runner, results) = cli.run(options, args)
|
||||
for result in results['contacted'].values():
|
||||
if 'failed' in result or result.get('rc', 0) != 0:
|
||||
sys.exit(2)
|
||||
if results['dark']:
|
||||
sys.exit(3)
|
||||
except errors.AnsibleError, e:
|
||||
# Generic handler for ansible specific errors
|
||||
callbacks.display("ERROR: %s" % str(e), stderr=True, color='red')
|
||||
sys.exit(1)
|
||||
|
||||
sub = None
|
||||
try:
|
||||
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:
|
||||
raise AnsibleError("Unknown Ansible alias: %s" % me)
|
||||
except ImportError as e:
|
||||
# ImportError members have changed in py3
|
||||
if 'msg' in dir(e):
|
||||
msg = e.msg
|
||||
else:
|
||||
msg = e.message
|
||||
if msg.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()
|
||||
|
||||
except AnsibleOptionsError as e:
|
||||
cli.parser.print_help()
|
||||
display.error(to_text(e), wrap_text=False)
|
||||
exit_code = 5
|
||||
except AnsibleParserError as e:
|
||||
display.error(to_text(e), wrap_text=False)
|
||||
exit_code = 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
|
||||
# except AnsibleHostFailed as e:
|
||||
# display.error(str(e))
|
||||
# exit_code = 2
|
||||
except AnsibleError as e:
|
||||
display.error(to_text(e), wrap_text=False)
|
||||
exit_code = 1
|
||||
except KeyboardInterrupt:
|
||||
display.error("User interrupted execution")
|
||||
exit_code = 99
|
||||
except Exception as e:
|
||||
have_cli_options = cli is not None and cli.options is not None
|
||||
display.error("Unexpected Exception: %s" % to_text(e), wrap_text=False)
|
||||
if not have_cli_options or have_cli_options and cli.options.verbosity > 2:
|
||||
log_only = False
|
||||
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)
|
||||
|
||||
@ -1 +0,0 @@
|
||||
ansible
|
||||
@ -1 +0,0 @@
|
||||
ansible
|
||||
295
bin/ansible-doc
Executable file
295
bin/ansible-doc
Executable file
@ -0,0 +1,295 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# (c) 2012, Jan-Piet Mens <jpmens () gmail.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
import re
|
||||
import optparse
|
||||
import datetime
|
||||
import subprocess
|
||||
from ansible import utils
|
||||
from ansible.utils import module_docs
|
||||
import ansible.constants as C
|
||||
from ansible.utils import version
|
||||
import traceback
|
||||
|
||||
MODULEDIR = C.DEFAULT_MODULE_PATH
|
||||
|
||||
BLACKLIST_EXTS = ('.swp', '.bak', '~', '.rpm')
|
||||
|
||||
_ITALIC = re.compile(r"I\(([^)]+)\)")
|
||||
_BOLD = re.compile(r"B\(([^)]+)\)")
|
||||
_MODULE = re.compile(r"M\(([^)]+)\)")
|
||||
_URL = re.compile(r"U\(([^)]+)\)")
|
||||
_CONST = re.compile(r"C\(([^)]+)\)")
|
||||
PAGER = 'less'
|
||||
LESS_OPTS = 'FRSX' # -F (quit-if-one-screen) -R (allow raw ansi control chars)
|
||||
# -S (chop long lines) -X (disable termcap init and de-init)
|
||||
|
||||
def pager_print(text):
|
||||
''' just print text '''
|
||||
print text
|
||||
|
||||
def pager_pipe(text, cmd):
|
||||
''' pipe text through a pager '''
|
||||
if 'LESS' not in os.environ:
|
||||
os.environ['LESS'] = LESS_OPTS
|
||||
try:
|
||||
cmd = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=sys.stdout)
|
||||
cmd.communicate(input=text)
|
||||
except IOError:
|
||||
pass
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
def pager(text):
|
||||
''' find reasonable way to display text '''
|
||||
# this is a much simpler form of what is in pydoc.py
|
||||
if not sys.stdout.isatty():
|
||||
pager_print(text)
|
||||
elif 'PAGER' in os.environ:
|
||||
if sys.platform == 'win32':
|
||||
pager_print(text)
|
||||
else:
|
||||
pager_pipe(text, os.environ['PAGER'])
|
||||
elif hasattr(os, 'system') and os.system('(less) 2> /dev/null') == 0:
|
||||
pager_pipe(text, 'less')
|
||||
else:
|
||||
pager_print(text)
|
||||
|
||||
def tty_ify(text):
|
||||
|
||||
t = _ITALIC.sub("`" + r"\1" + "'", text) # I(word) => `word'
|
||||
t = _BOLD.sub("*" + r"\1" + "*", t) # B(word) => *word*
|
||||
t = _MODULE.sub("[" + r"\1" + "]", t) # M(word) => [word]
|
||||
t = _URL.sub(r"\1", t) # U(word) => word
|
||||
t = _CONST.sub("`" + r"\1" + "'", t) # C(word) => `word'
|
||||
|
||||
return t
|
||||
|
||||
def get_man_text(doc):
|
||||
|
||||
opt_indent=" "
|
||||
text = []
|
||||
text.append("> %s\n" % doc['module'].upper())
|
||||
|
||||
desc = "".join(doc['description'])
|
||||
|
||||
text.append("%s\n" % textwrap.fill(tty_ify(desc), initial_indent=" ", subsequent_indent=" "))
|
||||
|
||||
if 'option_keys' in doc and len(doc['option_keys']) > 0:
|
||||
text.append("Options (= is mandatory):\n")
|
||||
|
||||
for o in sorted(doc['option_keys']):
|
||||
opt = doc['options'][o]
|
||||
|
||||
if opt.get('required', False):
|
||||
opt_leadin = "="
|
||||
else:
|
||||
opt_leadin = "-"
|
||||
|
||||
text.append("%s %s" % (opt_leadin, o))
|
||||
|
||||
desc = "".join(opt['description'])
|
||||
|
||||
if 'choices' in opt:
|
||||
choices = ", ".join(str(i) for i in opt['choices'])
|
||||
desc = desc + " (Choices: " + choices + ")"
|
||||
if 'default' in opt:
|
||||
default = str(opt['default'])
|
||||
desc = desc + " [Default: " + default + "]"
|
||||
text.append("%s\n" % textwrap.fill(tty_ify(desc), initial_indent=opt_indent,
|
||||
subsequent_indent=opt_indent))
|
||||
|
||||
if 'notes' in doc and len(doc['notes']) > 0:
|
||||
notes = "".join(doc['notes'])
|
||||
text.append("Notes:%s\n" % textwrap.fill(tty_ify(notes), initial_indent=" ",
|
||||
subsequent_indent=opt_indent))
|
||||
|
||||
|
||||
if 'requirements' in doc and doc['requirements'] is not None and len(doc['requirements']) > 0:
|
||||
req = ", ".join(doc['requirements'])
|
||||
text.append("Requirements:%s\n" % textwrap.fill(tty_ify(req), initial_indent=" ",
|
||||
subsequent_indent=opt_indent))
|
||||
|
||||
if 'examples' in doc and len(doc['examples']) > 0:
|
||||
text.append("Example%s:\n" % ('' if len(doc['examples']) < 2 else 's'))
|
||||
for ex in doc['examples']:
|
||||
text.append("%s\n" % (ex['code']))
|
||||
|
||||
if 'plainexamples' in doc and doc['plainexamples'] is not None:
|
||||
text.append(doc['plainexamples'])
|
||||
text.append('')
|
||||
|
||||
return "\n".join(text)
|
||||
|
||||
|
||||
def get_snippet_text(doc):
|
||||
|
||||
text = []
|
||||
desc = tty_ify("".join(doc['short_description']))
|
||||
text.append("- name: %s" % (desc))
|
||||
text.append(" action: %s" % (doc['module']))
|
||||
|
||||
for o in sorted(doc['options'].keys()):
|
||||
opt = doc['options'][o]
|
||||
desc = tty_ify("".join(opt['description']))
|
||||
|
||||
if opt.get('required', False):
|
||||
s = o + "="
|
||||
else:
|
||||
s = o
|
||||
|
||||
text.append(" %-20s # %s" % (s, desc))
|
||||
text.append('')
|
||||
|
||||
return "\n".join(text)
|
||||
|
||||
def get_module_list_text(module_list):
|
||||
text = []
|
||||
for module in sorted(set(module_list)):
|
||||
|
||||
if module in module_docs.BLACKLIST_MODULES:
|
||||
continue
|
||||
|
||||
filename = utils.plugins.module_finder.find_plugin(module)
|
||||
|
||||
if filename is None:
|
||||
continue
|
||||
if filename.endswith(".ps1"):
|
||||
continue
|
||||
if os.path.isdir(filename):
|
||||
continue
|
||||
|
||||
try:
|
||||
doc, plainexamples = module_docs.get_docstring(filename)
|
||||
desc = tty_ify(doc.get('short_description', '?'))
|
||||
if len(desc) > 55:
|
||||
desc = desc + '...'
|
||||
text.append("%-20s %-60.60s" % (module, desc))
|
||||
except:
|
||||
traceback.print_exc()
|
||||
sys.stderr.write("ERROR: module %s has a documentation error formatting or is missing documentation\n" % module)
|
||||
return "\n".join(text)
|
||||
|
||||
def main():
|
||||
|
||||
p = optparse.OptionParser(
|
||||
version=version("%prog"),
|
||||
usage='usage: %prog [options] [module...]',
|
||||
description='Show Ansible module documentation',
|
||||
)
|
||||
|
||||
p.add_option("-M", "--module-path",
|
||||
action="store",
|
||||
dest="module_path",
|
||||
default=MODULEDIR,
|
||||
help="Ansible modules/ directory")
|
||||
p.add_option("-l", "--list",
|
||||
action="store_true",
|
||||
default=False,
|
||||
dest='list_dir',
|
||||
help='List available modules')
|
||||
p.add_option("-s", "--snippet",
|
||||
action="store_true",
|
||||
default=False,
|
||||
dest='show_snippet',
|
||||
help='Show playbook snippet for specified module(s)')
|
||||
p.add_option('-v', action='version', help='Show version number and exit')
|
||||
|
||||
(options, args) = p.parse_args()
|
||||
|
||||
if options.module_path is not None:
|
||||
for i in options.module_path.split(os.pathsep):
|
||||
utils.plugins.module_finder.add_directory(i)
|
||||
|
||||
if options.list_dir:
|
||||
# list all modules
|
||||
paths = utils.plugins.module_finder._get_paths()
|
||||
module_list = []
|
||||
for path in paths:
|
||||
# os.system("ls -C %s" % (path))
|
||||
if os.path.isdir(path):
|
||||
for module in os.listdir(path):
|
||||
if any(module.endswith(x) for x in BLACKLIST_EXTS):
|
||||
continue
|
||||
module_list.append(module)
|
||||
|
||||
pager(get_module_list_text(module_list))
|
||||
sys.exit()
|
||||
|
||||
if len(args) == 0:
|
||||
p.print_help()
|
||||
|
||||
def print_paths(finder):
|
||||
''' Returns a string suitable for printing of the search path '''
|
||||
|
||||
# Uses a list to get the order right
|
||||
ret = []
|
||||
for i in finder._get_paths():
|
||||
if i not in ret:
|
||||
ret.append(i)
|
||||
return os.pathsep.join(ret)
|
||||
|
||||
text = ''
|
||||
for module in args:
|
||||
|
||||
filename = utils.plugins.module_finder.find_plugin(module)
|
||||
if filename is None:
|
||||
sys.stderr.write("module %s not found in %s\n" % (module,
|
||||
print_paths(utils.plugins.module_finder)))
|
||||
continue
|
||||
|
||||
if any(filename.endswith(x) for x in BLACKLIST_EXTS):
|
||||
continue
|
||||
|
||||
try:
|
||||
doc, plainexamples = module_docs.get_docstring(filename)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
sys.stderr.write("ERROR: module %s has a documentation error formatting or is missing documentation\n" % module)
|
||||
continue
|
||||
|
||||
if doc is not None:
|
||||
|
||||
all_keys = []
|
||||
for (k,v) in doc['options'].iteritems():
|
||||
all_keys.append(k)
|
||||
all_keys = sorted(all_keys)
|
||||
doc['option_keys'] = all_keys
|
||||
|
||||
doc['filename'] = filename
|
||||
doc['docuri'] = doc['module'].replace('_', '-')
|
||||
doc['now_date'] = datetime.date.today().strftime('%Y-%m-%d')
|
||||
doc['plainexamples'] = plainexamples
|
||||
|
||||
if options.show_snippet:
|
||||
text += get_snippet_text(doc)
|
||||
else:
|
||||
text += get_man_text(doc)
|
||||
else:
|
||||
# this typically means we couldn't even parse the docstring, not just that the YAML is busted,
|
||||
# probably a quoting issue.
|
||||
sys.stderr.write("ERROR: module %s missing documentation (or could not parse documentation)\n" % module)
|
||||
pager(text)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -1 +0,0 @@
|
||||
ansible
|
||||
822
bin/ansible-galaxy
Executable file
822
bin/ansible-galaxy
Executable file
@ -0,0 +1,822 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
########################################################################
|
||||
#
|
||||
# (C) 2013, James Cammarata <jcammarata@ansible.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
########################################################################
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import os.path
|
||||
import shutil
|
||||
import sys
|
||||
import tarfile
|
||||
import tempfile
|
||||
import urllib
|
||||
import urllib2
|
||||
import yaml
|
||||
|
||||
from collections import defaultdict
|
||||
from distutils.version import LooseVersion
|
||||
from jinja2 import Environment
|
||||
from optparse import OptionParser
|
||||
|
||||
import ansible.constants as C
|
||||
|
||||
default_meta_template = """---
|
||||
galaxy_info:
|
||||
author: {{ author }}
|
||||
description: {{description}}
|
||||
company: {{ company }}
|
||||
# Some suggested licenses:
|
||||
# - BSD (default)
|
||||
# - MIT
|
||||
# - GPLv2
|
||||
# - GPLv3
|
||||
# - Apache
|
||||
# - CC-BY
|
||||
license: {{ license }}
|
||||
min_ansible_version: {{ min_ansible_version }}
|
||||
#
|
||||
# Below are all platforms currently available. Just uncomment
|
||||
# the ones that apply to your role. If you don't see your
|
||||
# platform on this list, let us know and we'll get it added!
|
||||
#
|
||||
#platforms:
|
||||
{%- for platform,versions in platforms.iteritems() %}
|
||||
#- name: {{ platform }}
|
||||
# versions:
|
||||
# - all
|
||||
{%- for version in versions %}
|
||||
# - {{ version }}
|
||||
{%- endfor %}
|
||||
{%- endfor %}
|
||||
#
|
||||
# Below are all categories currently available. Just as with
|
||||
# the platforms above, uncomment those that apply to your role.
|
||||
#
|
||||
#categories:
|
||||
{%- for category in categories %}
|
||||
#- {{ category.name }}
|
||||
{%- endfor %}
|
||||
dependencies: []
|
||||
# List your role dependencies here, one per line. Only
|
||||
# dependencies available via galaxy should be listed here.
|
||||
# Be sure to remove the '[]' above if you add dependencies
|
||||
# to this list.
|
||||
{% for dependency in dependencies %}
|
||||
#- {{ dependency }}
|
||||
{% endfor %}
|
||||
|
||||
"""
|
||||
|
||||
default_readme_template = """Role Name
|
||||
=========
|
||||
|
||||
A brief description of the role goes here.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
Any pre-requisites that may not be covered by Ansible itself or the role should be mentioned here. For instance, if the role uses the EC2 module, it may be a good idea to mention in this section that the boto package is required.
|
||||
|
||||
Role Variables
|
||||
--------------
|
||||
|
||||
A description of the settable variables for this role should go here, including any variables that are in defaults/main.yml, vars/main.yml, and any variables that can/should be set via parameters to the role. Any variables that are read from other roles and/or the global scope (ie. hostvars, group vars, etc.) should be mentioned here as well.
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
A list of other roles hosted on Galaxy should go here, plus any details in regards to parameters that may need to be set for other roles, or variables that are used from other roles.
|
||||
|
||||
Example Playbook
|
||||
----------------
|
||||
|
||||
Including an example of how to use your role (for instance, with variables passed in as parameters) is always nice for users too:
|
||||
|
||||
- hosts: servers
|
||||
roles:
|
||||
- { role: username.rolename, x: 42 }
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
BSD
|
||||
|
||||
Author Information
|
||||
------------------
|
||||
|
||||
An optional section for the role authors to include contact information, or a website (HTML is not allowed).
|
||||
"""
|
||||
|
||||
#-------------------------------------------------------------------------------------
|
||||
# Utility functions for parsing actions/options
|
||||
#-------------------------------------------------------------------------------------
|
||||
|
||||
VALID_ACTIONS = ("init", "info", "install", "list", "remove")
|
||||
|
||||
def get_action(args):
|
||||
"""
|
||||
Get the action the user wants to execute from the
|
||||
sys argv list.
|
||||
"""
|
||||
for i in range(0,len(args)):
|
||||
arg = args[i]
|
||||
if arg in VALID_ACTIONS:
|
||||
del args[i]
|
||||
return arg
|
||||
return None
|
||||
|
||||
def build_option_parser(action):
|
||||
"""
|
||||
Builds an option parser object based on the action
|
||||
the user wants to execute.
|
||||
"""
|
||||
|
||||
usage = "usage: %%prog [%s] [--help] [options] ..." % "|".join(VALID_ACTIONS)
|
||||
epilog = "\nSee '%s <command> --help' for more information on a specific command.\n\n" % os.path.basename(sys.argv[0])
|
||||
OptionParser.format_epilog = lambda self, formatter: self.epilog
|
||||
parser = OptionParser(usage=usage, epilog=epilog)
|
||||
|
||||
if not action:
|
||||
parser.print_help()
|
||||
sys.exit()
|
||||
|
||||
# options for all actions
|
||||
# - none yet
|
||||
|
||||
# options specific to actions
|
||||
if action == "info":
|
||||
parser.set_usage("usage: %prog info [options] role_name[,version]")
|
||||
elif action == "init":
|
||||
parser.set_usage("usage: %prog init [options] role_name")
|
||||
parser.add_option(
|
||||
'-p', '--init-path', dest='init_path', default="./",
|
||||
help='The path in which the skeleton role will be created. '
|
||||
'The default is the current working directory.')
|
||||
elif action == "install":
|
||||
parser.set_usage("usage: %prog install [options] [-r FILE | role_name(s)[,version] | tar_file(s)]")
|
||||
parser.add_option(
|
||||
'-i', '--ignore-errors', dest='ignore_errors', action='store_true', default=False,
|
||||
help='Ignore errors and continue with the next specified role.')
|
||||
parser.add_option(
|
||||
'-n', '--no-deps', dest='no_deps', action='store_true', default=False,
|
||||
help='Don\'t download roles listed as dependencies')
|
||||
parser.add_option(
|
||||
'-r', '--role-file', dest='role_file',
|
||||
help='A file containing a list of roles to be imported')
|
||||
elif action == "remove":
|
||||
parser.set_usage("usage: %prog remove role1 role2 ...")
|
||||
elif action == "list":
|
||||
parser.set_usage("usage: %prog list [role_name]")
|
||||
|
||||
# options that apply to more than one action
|
||||
if action != "init":
|
||||
parser.add_option(
|
||||
'-p', '--roles-path', dest='roles_path', default=C.DEFAULT_ROLES_PATH,
|
||||
help='The path to the directory containing your roles. '
|
||||
'The default is the roles_path configured in your '
|
||||
'ansible.cfg file (/etc/ansible/roles if not configured)')
|
||||
|
||||
if action in ("info","init","install"):
|
||||
parser.add_option(
|
||||
'-s', '--server', dest='api_server', default="galaxy.ansible.com",
|
||||
help='The API server destination')
|
||||
|
||||
if action in ("init","install"):
|
||||
parser.add_option(
|
||||
'-f', '--force', dest='force', action='store_true', default=False,
|
||||
help='Force overwriting an existing role')
|
||||
# done, return the parser
|
||||
return parser
|
||||
|
||||
def get_opt(options, k, defval=""):
|
||||
"""
|
||||
Returns an option from an Optparse values instance.
|
||||
"""
|
||||
try:
|
||||
data = getattr(options, k)
|
||||
except:
|
||||
return defval
|
||||
if k == "roles_path":
|
||||
if os.pathsep in data:
|
||||
data = data.split(os.pathsep)[0]
|
||||
return data
|
||||
|
||||
def exit_without_ignore(options, rc=1):
|
||||
"""
|
||||
Exits with the specified return code unless the
|
||||
option --ignore-errors was specified
|
||||
"""
|
||||
|
||||
if not get_opt(options, "ignore_errors", False):
|
||||
print 'You can use --ignore-errors to skip failed roles.'
|
||||
sys.exit(rc)
|
||||
|
||||
#-------------------------------------------------------------------------------------
|
||||
# Galaxy API functions
|
||||
#-------------------------------------------------------------------------------------
|
||||
|
||||
def api_get_config(api_server):
|
||||
"""
|
||||
Fetches the Galaxy API current version to ensure
|
||||
the API server is up and reachable.
|
||||
"""
|
||||
|
||||
try:
|
||||
url = 'https://%s/api/' % api_server
|
||||
data = json.load(urllib2.urlopen(url))
|
||||
if not data.get("current_version",None):
|
||||
return None
|
||||
else:
|
||||
return data
|
||||
except:
|
||||
return None
|
||||
|
||||
def api_lookup_role_by_name(api_server, role_name):
|
||||
"""
|
||||
Uses the Galaxy API to do a lookup on the role owner/name.
|
||||
"""
|
||||
|
||||
role_name = urllib.quote(role_name)
|
||||
|
||||
try:
|
||||
parts = role_name.split(".")
|
||||
user_name = ".".join(parts[0:-1])
|
||||
role_name = parts[-1]
|
||||
print " downloading role '%s', owned by %s" % (role_name, user_name)
|
||||
except:
|
||||
parser.print_help()
|
||||
print "Invalid role name (%s). You must specify username.rolename" % role_name
|
||||
sys.exit(1)
|
||||
|
||||
url = 'https://%s/api/v1/roles/?owner__username=%s&name=%s' % (api_server,user_name,role_name)
|
||||
try:
|
||||
data = json.load(urllib2.urlopen(url))
|
||||
if len(data["results"]) == 0:
|
||||
return None
|
||||
else:
|
||||
return data["results"][0]
|
||||
except:
|
||||
return None
|
||||
|
||||
def api_fetch_role_related(api_server, related, role_id):
|
||||
"""
|
||||
Uses the Galaxy API to fetch the list of related items for
|
||||
the given role. The url comes from the 'related' field of
|
||||
the role.
|
||||
"""
|
||||
|
||||
try:
|
||||
url = 'https://%s/api/v1/roles/%d/%s/?page_size=50' % (api_server, int(role_id), related)
|
||||
data = json.load(urllib2.urlopen(url))
|
||||
results = data['results']
|
||||
done = (data.get('next', None) == None)
|
||||
while not done:
|
||||
url = 'https://%s%s' % (api_server, data['next'])
|
||||
print url
|
||||
data = json.load(urllib2.urlopen(url))
|
||||
results += data['results']
|
||||
done = (data.get('next', None) == None)
|
||||
return results
|
||||
except:
|
||||
return None
|
||||
|
||||
def api_get_list(api_server, what):
|
||||
"""
|
||||
Uses the Galaxy API to fetch the list of items specified.
|
||||
"""
|
||||
|
||||
try:
|
||||
url = 'https://%s/api/v1/%s/?page_size' % (api_server, what)
|
||||
data = json.load(urllib2.urlopen(url))
|
||||
if "results" in data:
|
||||
results = data['results']
|
||||
else:
|
||||
results = data
|
||||
done = True
|
||||
if "next" in data:
|
||||
done = (data.get('next', None) == None)
|
||||
while not done:
|
||||
url = 'https://%s%s' % (api_server, data['next'])
|
||||
print url
|
||||
data = json.load(urllib2.urlopen(url))
|
||||
results += data['results']
|
||||
done = (data.get('next', None) == None)
|
||||
return results
|
||||
except:
|
||||
print " - failed to download the %s list" % what
|
||||
return None
|
||||
|
||||
#-------------------------------------------------------------------------------------
|
||||
# Role utility functions
|
||||
#-------------------------------------------------------------------------------------
|
||||
|
||||
def get_role_path(role_name, options):
|
||||
"""
|
||||
Returns the role path based on the roles_path option
|
||||
and the role name.
|
||||
"""
|
||||
roles_path = get_opt(options,'roles_path')
|
||||
roles_path = os.path.join(roles_path, role_name)
|
||||
roles_path = os.path.expanduser(roles_path)
|
||||
return roles_path
|
||||
|
||||
def get_role_metadata(role_name, options):
|
||||
"""
|
||||
Returns the metadata as YAML, if the file 'meta/main.yml'
|
||||
exists in the specified role_path
|
||||
"""
|
||||
role_path = os.path.join(get_role_path(role_name, options), 'meta/main.yml')
|
||||
try:
|
||||
if os.path.isfile(role_path):
|
||||
f = open(role_path, 'r')
|
||||
meta_data = yaml.safe_load(f)
|
||||
f.close()
|
||||
return meta_data
|
||||
else:
|
||||
return None
|
||||
except:
|
||||
return None
|
||||
|
||||
def get_galaxy_install_info(role_name, options):
|
||||
"""
|
||||
Returns the YAML data contained in 'meta/.galaxy_install_info',
|
||||
if it exists.
|
||||
"""
|
||||
|
||||
try:
|
||||
info_path = os.path.join(get_role_path(role_name, options), 'meta/.galaxy_install_info')
|
||||
if os.path.isfile(info_path):
|
||||
f = open(info_path, 'r')
|
||||
info_data = yaml.safe_load(f)
|
||||
f.close()
|
||||
return info_data
|
||||
else:
|
||||
return None
|
||||
except:
|
||||
return None
|
||||
|
||||
def write_galaxy_install_info(role_name, role_version, options):
|
||||
"""
|
||||
Writes a YAML-formatted file to the role's meta/ directory
|
||||
(named .galaxy_install_info) which contains some information
|
||||
we can use later for commands like 'list' and 'info'.
|
||||
"""
|
||||
|
||||
info = dict(
|
||||
version = role_version,
|
||||
install_date = datetime.datetime.utcnow().strftime("%c"),
|
||||
)
|
||||
try:
|
||||
info_path = os.path.join(get_role_path(role_name, options), 'meta/.galaxy_install_info')
|
||||
f = open(info_path, 'w+')
|
||||
info_data = yaml.safe_dump(info, f)
|
||||
f.close()
|
||||
except:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def remove_role(role_name, options):
|
||||
"""
|
||||
Removes the specified role from the roles path. There is a
|
||||
sanity check to make sure there's a meta/main.yml file at this
|
||||
path so the user doesn't blow away random directories
|
||||
"""
|
||||
if get_role_metadata(role_name, options):
|
||||
role_path = get_role_path(role_name, options)
|
||||
shutil.rmtree(role_path)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def fetch_role(role_name, target, role_data, options):
|
||||
"""
|
||||
Downloads the archived role from github to a temp location, extracts
|
||||
it, and then copies the extracted role to the role library path.
|
||||
"""
|
||||
|
||||
# first grab the file and save it to a temp location
|
||||
archive_url = 'https://github.com/%s/%s/archive/%s.tar.gz' % (role_data["github_user"], role_data["github_repo"], target)
|
||||
print " - downloading role from %s" % archive_url
|
||||
|
||||
try:
|
||||
url_file = urllib2.urlopen(archive_url)
|
||||
temp_file = tempfile.NamedTemporaryFile(delete=False)
|
||||
data = url_file.read()
|
||||
while data:
|
||||
temp_file.write(data)
|
||||
data = url_file.read()
|
||||
temp_file.close()
|
||||
return temp_file.name
|
||||
except Exception, e:
|
||||
# TODO: better urllib2 error handling for error
|
||||
# messages that are more exact
|
||||
print "Error: failed to download the file."
|
||||
return False
|
||||
|
||||
def install_role(role_name, role_version, role_filename, options):
|
||||
# the file is a tar, so open it that way and extract it
|
||||
# to the specified (or default) roles directory
|
||||
if not tarfile.is_tarfile(role_filename):
|
||||
print "Error: the file downloaded was not a tar.gz"
|
||||
return False
|
||||
else:
|
||||
role_tar_file = tarfile.open(role_filename, "r:gz")
|
||||
# verify the role's meta file
|
||||
meta_file = None
|
||||
members = role_tar_file.getmembers()
|
||||
# next find the metadata file
|
||||
for member in members:
|
||||
if "/meta/main.yml" in member.name:
|
||||
meta_file = member
|
||||
break
|
||||
if not meta_file:
|
||||
print "Error: this role does not appear to have a meta/main.yml file."
|
||||
return False
|
||||
else:
|
||||
try:
|
||||
meta_file_data = yaml.safe_load(role_tar_file.extractfile(meta_file))
|
||||
except:
|
||||
print "Error: this role does not appear to have a valid meta/main.yml file."
|
||||
return False
|
||||
|
||||
# we strip off the top-level directory for all of the files contained within
|
||||
# the tar file here, since the default is 'github_repo-target', and change it
|
||||
# to the specified role's name
|
||||
role_path = os.path.join(get_opt(options, 'roles_path', '/etc/ansible/roles'), role_name)
|
||||
role_path = os.path.expanduser(role_path)
|
||||
print " - extracting %s to %s" % (role_name, role_path)
|
||||
try:
|
||||
if os.path.exists(role_path):
|
||||
if not os.path.isdir(role_path):
|
||||
print "Error: the specified roles path exists and is not a directory."
|
||||
return False
|
||||
elif not get_opt(options, "force", False):
|
||||
print "Error: the specified role %s appears to already exist. Use --force to replace it." % role_name
|
||||
return False
|
||||
else:
|
||||
# using --force, remove the old path
|
||||
if not remove_role(role_name, options):
|
||||
print "Error: %s doesn't appear to contain a role." % role_path
|
||||
print "Please remove this directory manually if you really want to put the role here."
|
||||
return False
|
||||
else:
|
||||
os.makedirs(role_path)
|
||||
|
||||
# now we do the actual extraction to the role_path
|
||||
for member in members:
|
||||
# we only extract files, and remove any relative path
|
||||
# bits that might be in the file for security purposes
|
||||
# and drop the leading directory, as mentioned above
|
||||
if member.isreg():
|
||||
parts = member.name.split("/")[1:]
|
||||
final_parts = []
|
||||
for part in parts:
|
||||
if part != '..' and '~' not in part and '$' not in part:
|
||||
final_parts.append(part)
|
||||
member.name = os.path.join(*final_parts)
|
||||
role_tar_file.extract(member, role_path)
|
||||
|
||||
# write out the install info file for later use
|
||||
write_galaxy_install_info(role_name, role_version, options)
|
||||
except OSError, e:
|
||||
print "Error: you do not have permission to modify files in %s" % role_path
|
||||
return False
|
||||
|
||||
# return the parsed yaml metadata
|
||||
print "%s was installed successfully" % role_name
|
||||
return meta_file_data
|
||||
|
||||
#-------------------------------------------------------------------------------------
|
||||
# Action functions
|
||||
#-------------------------------------------------------------------------------------
|
||||
|
||||
def execute_init(args, options, parser):
|
||||
"""
|
||||
Executes the init action, which creates the skeleton framework
|
||||
of a role that complies with the galaxy metadata format.
|
||||
"""
|
||||
|
||||
init_path = get_opt(options, 'init_path', './')
|
||||
api_server = get_opt(options, "api_server", "galaxy.ansible.com")
|
||||
force = get_opt(options, 'force', False)
|
||||
|
||||
api_config = api_get_config(api_server)
|
||||
if not api_config:
|
||||
print "The API server (%s) is not responding, please try again later." % api_server
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
role_name = args.pop(0).strip()
|
||||
if role_name == "":
|
||||
raise Exception("")
|
||||
role_path = os.path.join(init_path, role_name)
|
||||
if os.path.exists(role_path):
|
||||
if os.path.isfile(role_path):
|
||||
print "The path %s already exists, but is a file - aborting" % role_path
|
||||
sys.exit(1)
|
||||
elif not force:
|
||||
print "The directory %s already exists." % role_path
|
||||
print ""
|
||||
print "You can use --force to re-initialize this directory,\n" + \
|
||||
"however it will reset any main.yml files that may have\n" + \
|
||||
"been modified there already."
|
||||
sys.exit(1)
|
||||
except Exception, e:
|
||||
parser.print_help()
|
||||
print "No role name specified for init"
|
||||
sys.exit(1)
|
||||
|
||||
ROLE_DIRS = ('defaults','files','handlers','meta','tasks','templates','vars')
|
||||
|
||||
# create the default README.md
|
||||
if not os.path.exists(role_path):
|
||||
os.makedirs(role_path)
|
||||
readme_path = os.path.join(role_path, "README.md")
|
||||
f = open(readme_path, "wb")
|
||||
f.write(default_readme_template)
|
||||
f.close
|
||||
|
||||
for dir in ROLE_DIRS:
|
||||
dir_path = os.path.join(init_path, role_name, dir)
|
||||
main_yml_path = os.path.join(dir_path, 'main.yml')
|
||||
# create the directory if it doesn't exist already
|
||||
if not os.path.exists(dir_path):
|
||||
os.makedirs(dir_path)
|
||||
|
||||
# now create the main.yml file for that directory
|
||||
if dir == "meta":
|
||||
# create a skeleton meta/main.yml with a valid galaxy_info
|
||||
# datastructure in place, plus with all of the available
|
||||
# tags/platforms included (but commented out) and the
|
||||
# dependencies section
|
||||
platforms = api_get_list(api_server, "platforms")
|
||||
if not platforms:
|
||||
platforms = []
|
||||
categories = api_get_list(api_server, "categories")
|
||||
if not categories:
|
||||
categories = []
|
||||
|
||||
# group the list of platforms from the api based
|
||||
# on their names, with the release field being
|
||||
# appended to a list of versions
|
||||
platform_groups = defaultdict(list)
|
||||
for platform in platforms:
|
||||
platform_groups[platform['name']].append(platform['release'])
|
||||
platform_groups[platform['name']].sort()
|
||||
|
||||
inject = dict(
|
||||
author = 'your name',
|
||||
company = 'your company (optional)',
|
||||
license = 'license (GPLv2, CC-BY, etc)',
|
||||
min_ansible_version = '1.2',
|
||||
platforms = platform_groups,
|
||||
categories = categories,
|
||||
)
|
||||
rendered_meta = Environment().from_string(default_meta_template).render(inject)
|
||||
f = open(main_yml_path, 'w')
|
||||
f.write(rendered_meta)
|
||||
f.close()
|
||||
pass
|
||||
elif dir not in ('files','templates'):
|
||||
# just write a (mostly) empty YAML file for main.yml
|
||||
f = open(main_yml_path, 'w')
|
||||
f.write('---\n# %s file for %s\n' % (dir,role_name))
|
||||
f.close()
|
||||
print "%s was created successfully" % role_name
|
||||
|
||||
def execute_info(args, options, parser):
|
||||
"""
|
||||
Executes the info action. This action prints out detailed
|
||||
information about an installed role as well as info available
|
||||
from the galaxy API.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
def execute_install(args, options, parser):
|
||||
"""
|
||||
Executes the installation action. The args list contains the
|
||||
roles to be installed, unless -f was specified. The list of roles
|
||||
can be a name (which will be downloaded via the galaxy API and github),
|
||||
or it can be a local .tar.gz file.
|
||||
"""
|
||||
|
||||
role_file = get_opt(options, "role_file", None)
|
||||
api_server = get_opt(options, "api_server", "galaxy.ansible.com")
|
||||
no_deps = get_opt(options, "no_deps", False)
|
||||
|
||||
if len(args) == 0 and not role_file:
|
||||
# the user needs to specify one of either --role-file
|
||||
# or specify a single user/role name
|
||||
parser.print_help()
|
||||
print "You must specify a user/role name or a roles file"
|
||||
sys.exit()
|
||||
elif len(args) == 1 and role_file:
|
||||
# using a role file is mutually exclusive of specifying
|
||||
# the role name on the command line
|
||||
parser.print_help()
|
||||
print "Please specify a user/role name, or a roles file, but not both"
|
||||
sys.exit(1)
|
||||
|
||||
api_config = api_get_config(api_server)
|
||||
if not api_config:
|
||||
print "The API server (%s) is not responding, please try again later." % api_server
|
||||
sys.exit(1)
|
||||
|
||||
roles_done = []
|
||||
if role_file:
|
||||
# roles listed in a file, one per line
|
||||
# so we'll go through and grab them all
|
||||
f = open(role_file, 'r')
|
||||
roles_left = f.readlines()
|
||||
f.close()
|
||||
else:
|
||||
# roles were specified directly, so we'll just go out grab them
|
||||
# (and their dependencies, unless the user doesn't want us to).
|
||||
roles_left = args
|
||||
|
||||
while len(roles_left) > 0:
|
||||
# query the galaxy API for the role data
|
||||
role_name = roles_left.pop(0).strip()
|
||||
role_version = None
|
||||
|
||||
if role_name == "" or role_name.startswith("#"):
|
||||
continue
|
||||
elif ',' in role_name:
|
||||
role_name,role_version = role_name.split(',',1)
|
||||
role_name = role_name.strip()
|
||||
role_version = role_version.strip()
|
||||
|
||||
if os.path.isfile(role_name):
|
||||
# installing a local tar.gz
|
||||
tar_file = role_name
|
||||
role_name = os.path.basename(role_name).replace('.tar.gz','')
|
||||
if tarfile.is_tarfile(tar_file):
|
||||
print " - installing %s as %s" % (tar_file, role_name)
|
||||
if not install_role(role_name, role_version, tar_file, options):
|
||||
exit_without_ignore(options)
|
||||
else:
|
||||
print "%s (%s) was NOT installed successfully." % (role_name,tar_file)
|
||||
exit_without_ignore(options)
|
||||
else:
|
||||
# installing remotely
|
||||
role_data = api_lookup_role_by_name(api_server, role_name)
|
||||
if not role_data:
|
||||
print "Sorry, %s was not found on %s." % (role_name, api_server)
|
||||
continue
|
||||
|
||||
role_versions = api_fetch_role_related(api_server, 'versions', role_data['id'])
|
||||
if not role_version:
|
||||
# convert the version names to LooseVersion objects
|
||||
# and sort them to get the latest version. If there
|
||||
# are no versions in the list, we'll grab the head
|
||||
# of the master branch
|
||||
if len(role_versions) > 0:
|
||||
loose_versions = [LooseVersion(a.get('name',None)) for a in role_versions]
|
||||
loose_versions.sort()
|
||||
role_version = str(loose_versions[-1])
|
||||
else:
|
||||
role_version = 'master'
|
||||
print " no version specified, installing %s" % role_version
|
||||
else:
|
||||
if role_versions and role_version not in [a.get('name',None) for a in role_versions]:
|
||||
print "The specified version (%s) was not found in the list of available versions." % role_version
|
||||
exit_without_ignore(options)
|
||||
continue
|
||||
|
||||
# download the role. if --no-deps was specified, we stop here,
|
||||
# otherwise we recursively grab roles and all of their deps.
|
||||
tmp_file = fetch_role(role_name, role_version, role_data, options)
|
||||
if tmp_file and install_role(role_name, role_version, tmp_file, options):
|
||||
# we're done with the temp file, clean it up
|
||||
os.unlink(tmp_file)
|
||||
# install dependencies, if we want them
|
||||
if not no_deps:
|
||||
role_dependencies = role_data['summary_fields']['dependencies'] # api_fetch_role_related(api_server, 'dependencies', role_data['id'])
|
||||
for dep_name in role_dependencies:
|
||||
#dep_name = "%s.%s" % (dep['owner'], dep['name'])
|
||||
if not get_role_metadata(dep_name, options):
|
||||
print ' adding dependency: %s' % dep_name
|
||||
roles_left.append(dep_name)
|
||||
else:
|
||||
print ' dependency %s is already installed, skipping.' % dep_name
|
||||
else:
|
||||
if tmp_file:
|
||||
os.unlink(tmp_file)
|
||||
print "%s was NOT installed successfully." % role_name
|
||||
exit_without_ignore(options)
|
||||
sys.exit(0)
|
||||
|
||||
def execute_remove(args, options, parser):
|
||||
"""
|
||||
Executes the remove action. The args list contains the list
|
||||
of roles to be removed. This list can contain more than one role.
|
||||
"""
|
||||
|
||||
if len(args) == 0:
|
||||
parser.print_help()
|
||||
print 'You must specify at least one role to remove.'
|
||||
sys.exit()
|
||||
|
||||
for role in args:
|
||||
if get_role_metadata(role, options):
|
||||
if remove_role(role, options):
|
||||
print 'successfully removed %s' % role
|
||||
else:
|
||||
print "failed to remove role: %s" % role
|
||||
else:
|
||||
print '%s is not installed, skipping.' % role
|
||||
sys.exit(0)
|
||||
|
||||
def execute_list(args, options, parser):
|
||||
"""
|
||||
Executes the list action. The args list can contain zero
|
||||
or one role. If one is specified, only that role will be
|
||||
shown, otherwise all roles in the specified directory will
|
||||
be shown.
|
||||
"""
|
||||
|
||||
if len(args) > 1:
|
||||
print "Please specify only one role to list, or specify no roles to see a full list"
|
||||
sys.exit(1)
|
||||
|
||||
if len(args) == 1:
|
||||
# show only the request role, if it exists
|
||||
role_name = args[0]
|
||||
metadata = get_role_metadata(role_name, options)
|
||||
if metadata:
|
||||
install_info = get_galaxy_install_info(role_name, options)
|
||||
version = None
|
||||
if install_info:
|
||||
version = install_info.get("version", None)
|
||||
if not version:
|
||||
version = "(unknown version)"
|
||||
# show some more info about single roles here
|
||||
print " %s, %s" % (role_name, version)
|
||||
else:
|
||||
print "The role %s was not found" % role_name
|
||||
else:
|
||||
# show all valid roles in the roles_path directory
|
||||
roles_path = get_opt(options, 'roles_path')
|
||||
roles_path = os.path.expanduser(roles_path)
|
||||
if not os.path.exists(roles_path):
|
||||
parser.print_help()
|
||||
print "The path %s does not exist. Please specify a valid path with --roles-path" % roles_path
|
||||
sys.exit(1)
|
||||
elif not os.path.isdir(roles_path):
|
||||
print "%s exists, but it is not a directory. Please specify a valid path with --roles-path" % roles_path
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
path_files = os.listdir(roles_path)
|
||||
for path_file in path_files:
|
||||
if get_role_metadata(path_file, options):
|
||||
install_info = get_galaxy_install_info(path_file, options)
|
||||
version = None
|
||||
if install_info:
|
||||
version = install_info.get("version", None)
|
||||
if not version:
|
||||
version = "(unknown version)"
|
||||
print " %s, %s" % (path_file, version)
|
||||
sys.exit(0)
|
||||
|
||||
#-------------------------------------------------------------------------------------
|
||||
# The main entry point
|
||||
#-------------------------------------------------------------------------------------
|
||||
|
||||
def main():
|
||||
# parse the CLI options
|
||||
action = get_action(sys.argv)
|
||||
parser = build_option_parser(action)
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
# execute the desired action
|
||||
if 1: #try:
|
||||
fn = globals()["execute_%s" % action]
|
||||
fn(args, options, parser)
|
||||
#except KeyError, e:
|
||||
# print "Error: %s is not a valid action. Valid actions are: %s" % (action, ", ".join(VALID_ACTIONS))
|
||||
# sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1 +0,0 @@
|
||||
ansible
|
||||
316
bin/ansible-playbook
Executable file
316
bin/ansible-playbook
Executable file
@ -0,0 +1,316 @@
|
||||
#!/usr/bin/env python
|
||||
# (C) 2012, Michael DeHaan, <michael.dehaan@gmail.com>
|
||||
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#######################################################
|
||||
|
||||
#__requires__ = ['ansible']
|
||||
#import pkg_resources
|
||||
|
||||
import sys
|
||||
import os
|
||||
import stat
|
||||
|
||||
import ansible.playbook
|
||||
import ansible.constants as C
|
||||
import ansible.utils.template
|
||||
from ansible import errors
|
||||
from ansible import callbacks
|
||||
from ansible import utils
|
||||
from ansible.color import ANSIBLE_COLOR, stringc
|
||||
from ansible.callbacks import display
|
||||
|
||||
def colorize(lead, num, color):
|
||||
""" Print 'lead' = 'num' in 'color' """
|
||||
if num != 0 and ANSIBLE_COLOR and color is not None:
|
||||
return "%s%s%-15s" % (stringc(lead, color), stringc("=", color), stringc(str(num), color))
|
||||
else:
|
||||
return "%s=%-4s" % (lead, str(num))
|
||||
|
||||
def hostcolor(host, stats, color=True):
|
||||
if ANSIBLE_COLOR and color:
|
||||
if stats['failures'] != 0 or stats['unreachable'] != 0:
|
||||
return "%-37s" % stringc(host, 'red')
|
||||
elif stats['changed'] != 0:
|
||||
return "%-37s" % stringc(host, 'yellow')
|
||||
else:
|
||||
return "%-37s" % stringc(host, 'green')
|
||||
return "%-26s" % host
|
||||
|
||||
|
||||
def main(args):
|
||||
''' run ansible-playbook operations '''
|
||||
|
||||
# create parser for CLI options
|
||||
parser = utils.base_parser(
|
||||
constants=C,
|
||||
usage = "%prog playbook.yml",
|
||||
connect_opts=True,
|
||||
runas_opts=True,
|
||||
subset_opts=True,
|
||||
check_opts=True,
|
||||
diff_opts=True
|
||||
)
|
||||
#parser.add_option('--vault-password', dest="vault_password",
|
||||
# help="password for vault encrypted files")
|
||||
parser.add_option('-e', '--extra-vars', dest="extra_vars", action="append",
|
||||
help="set additional variables as key=value or YAML/JSON", default=[])
|
||||
parser.add_option('-t', '--tags', dest='tags', default='all',
|
||||
help="only run plays and tasks tagged with these values")
|
||||
parser.add_option('--skip-tags', dest='skip_tags',
|
||||
help="only run plays and tasks whose tags do not match these values")
|
||||
parser.add_option('--syntax-check', dest='syntax', action='store_true',
|
||||
help="perform a syntax check on the playbook, but do not execute it")
|
||||
parser.add_option('--list-tasks', dest='listtasks', action='store_true',
|
||||
help="list all tasks that would be executed")
|
||||
parser.add_option('--step', dest='step', action='store_true',
|
||||
help="one-step-at-a-time: confirm each task before running")
|
||||
parser.add_option('--start-at-task', dest='start_at',
|
||||
help="start the playbook at the task matching this name")
|
||||
parser.add_option('--force-handlers', dest='force_handlers', action='store_true',
|
||||
help="run handlers even if a task fails")
|
||||
|
||||
options, args = parser.parse_args(args)
|
||||
|
||||
if len(args) == 0:
|
||||
parser.print_help(file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# su and sudo command line arguments need to be mutually exclusive
|
||||
if (options.su or options.su_user or options.ask_su_pass) and \
|
||||
(options.sudo or options.sudo_user or options.ask_sudo_pass):
|
||||
parser.error("Sudo arguments ('--sudo', '--sudo-user', and '--ask-sudo-pass') "
|
||||
"and su arguments ('-su', '--su-user', and '--ask-su-pass') are "
|
||||
"mutually exclusive")
|
||||
|
||||
if (options.ask_vault_pass and options.vault_password_file):
|
||||
parser.error("--ask-vault-pass and --vault-password-file are mutually exclusive")
|
||||
|
||||
sshpass = None
|
||||
sudopass = None
|
||||
su_pass = None
|
||||
vault_pass = None
|
||||
|
||||
options.ask_vault_pass = options.ask_vault_pass or C.DEFAULT_ASK_VAULT_PASS
|
||||
|
||||
if options.listhosts or options.syntax or options.listtasks:
|
||||
(_, _, _, vault_pass) = utils.ask_passwords(ask_vault_pass=options.ask_vault_pass)
|
||||
else:
|
||||
options.ask_pass = options.ask_pass or C.DEFAULT_ASK_PASS
|
||||
# Never ask for an SSH password when we run with local connection
|
||||
if options.connection == "local":
|
||||
options.ask_pass = False
|
||||
options.ask_sudo_pass = options.ask_sudo_pass or C.DEFAULT_ASK_SUDO_PASS
|
||||
options.ask_su_pass = options.ask_su_pass or C.DEFAULT_ASK_SU_PASS
|
||||
(sshpass, sudopass, su_pass, vault_pass) = utils.ask_passwords(ask_pass=options.ask_pass, ask_sudo_pass=options.ask_sudo_pass, ask_su_pass=options.ask_su_pass, ask_vault_pass=options.ask_vault_pass)
|
||||
options.sudo_user = options.sudo_user or C.DEFAULT_SUDO_USER
|
||||
options.su_user = options.su_user or C.DEFAULT_SU_USER
|
||||
|
||||
# read vault_pass from a file
|
||||
if not options.ask_vault_pass and options.vault_password_file:
|
||||
vault_pass = utils.read_vault_file(options.vault_password_file)
|
||||
|
||||
extra_vars = {}
|
||||
for extra_vars_opt in options.extra_vars:
|
||||
if extra_vars_opt.startswith("@"):
|
||||
# Argument is a YAML file (JSON is a subset of YAML)
|
||||
extra_vars = utils.combine_vars(extra_vars, utils.parse_yaml_from_file(extra_vars_opt[1:], vault_password=vault_pass))
|
||||
elif extra_vars_opt and extra_vars_opt[0] in '[{':
|
||||
# Arguments as YAML
|
||||
extra_vars = utils.combine_vars(extra_vars, utils.parse_yaml(extra_vars_opt))
|
||||
else:
|
||||
# Arguments as Key-value
|
||||
extra_vars = utils.combine_vars(extra_vars, utils.parse_kv(extra_vars_opt))
|
||||
|
||||
only_tags = options.tags.split(",")
|
||||
skip_tags = options.skip_tags
|
||||
if options.skip_tags is not None:
|
||||
skip_tags = options.skip_tags.split(",")
|
||||
|
||||
for playbook in args:
|
||||
if not os.path.exists(playbook):
|
||||
raise errors.AnsibleError("the playbook: %s could not be found" % playbook)
|
||||
if not (os.path.isfile(playbook) or stat.S_ISFIFO(os.stat(playbook).st_mode)):
|
||||
raise errors.AnsibleError("the playbook: %s does not appear to be a file" % playbook)
|
||||
|
||||
inventory = ansible.inventory.Inventory(options.inventory, vault_password=vault_pass)
|
||||
inventory.subset(options.subset)
|
||||
if len(inventory.list_hosts()) == 0:
|
||||
raise errors.AnsibleError("provided hosts list is empty")
|
||||
|
||||
# run all playbooks specified on the command line
|
||||
for playbook in args:
|
||||
|
||||
stats = callbacks.AggregateStats()
|
||||
playbook_cb = callbacks.PlaybookCallbacks(verbose=utils.VERBOSITY)
|
||||
if options.step:
|
||||
playbook_cb.step = options.step
|
||||
if options.start_at:
|
||||
playbook_cb.start_at = options.start_at
|
||||
runner_cb = callbacks.PlaybookRunnerCallbacks(stats, verbose=utils.VERBOSITY)
|
||||
|
||||
pb = ansible.playbook.PlayBook(
|
||||
playbook=playbook,
|
||||
module_path=options.module_path,
|
||||
inventory=inventory,
|
||||
forks=options.forks,
|
||||
remote_user=options.remote_user,
|
||||
remote_pass=sshpass,
|
||||
callbacks=playbook_cb,
|
||||
runner_callbacks=runner_cb,
|
||||
stats=stats,
|
||||
timeout=options.timeout,
|
||||
transport=options.connection,
|
||||
sudo=options.sudo,
|
||||
sudo_user=options.sudo_user,
|
||||
sudo_pass=sudopass,
|
||||
extra_vars=extra_vars,
|
||||
private_key_file=options.private_key_file,
|
||||
only_tags=only_tags,
|
||||
skip_tags=skip_tags,
|
||||
check=options.check,
|
||||
diff=options.diff,
|
||||
su=options.su,
|
||||
su_pass=su_pass,
|
||||
su_user=options.su_user,
|
||||
vault_password=vault_pass,
|
||||
force_handlers=options.force_handlers
|
||||
)
|
||||
|
||||
if options.listhosts or options.listtasks or options.syntax:
|
||||
print ''
|
||||
print 'playbook: %s' % playbook
|
||||
print ''
|
||||
playnum = 0
|
||||
for (play_ds, play_basedir) in zip(pb.playbook, pb.play_basedirs):
|
||||
playnum += 1
|
||||
play = ansible.playbook.Play(pb, play_ds, play_basedir,
|
||||
vault_password=pb.vault_password)
|
||||
label = play.name
|
||||
hosts = pb.inventory.list_hosts(play.hosts)
|
||||
|
||||
# Filter all tasks by given tags
|
||||
if pb.only_tags != 'all':
|
||||
if options.subset and not hosts:
|
||||
continue
|
||||
matched_tags, unmatched_tags = play.compare_tags(pb.only_tags)
|
||||
|
||||
# Remove skipped tasks
|
||||
matched_tags = matched_tags - set(pb.skip_tags)
|
||||
|
||||
unmatched_tags.discard('all')
|
||||
unknown_tags = ((set(pb.only_tags) | set(pb.skip_tags)) -
|
||||
(matched_tags | unmatched_tags))
|
||||
|
||||
if unknown_tags:
|
||||
continue
|
||||
|
||||
if options.listhosts:
|
||||
print ' play #%d (%s): host count=%d' % (playnum, label, len(hosts))
|
||||
for host in hosts:
|
||||
print ' %s' % host
|
||||
|
||||
if options.listtasks:
|
||||
print ' play #%d (%s):' % (playnum, label)
|
||||
|
||||
for task in play.tasks():
|
||||
if (set(task.tags).intersection(pb.only_tags) and not
|
||||
set(task.tags).intersection(pb.skip_tags)):
|
||||
if getattr(task, 'name', None) is not None:
|
||||
# meta tasks have no names
|
||||
print ' %s' % task.name
|
||||
print ''
|
||||
continue
|
||||
|
||||
if options.syntax:
|
||||
# if we've not exited by now then we are fine.
|
||||
print 'Playbook Syntax is fine'
|
||||
return 0
|
||||
|
||||
failed_hosts = []
|
||||
unreachable_hosts = []
|
||||
|
||||
try:
|
||||
|
||||
pb.run()
|
||||
|
||||
hosts = sorted(pb.stats.processed.keys())
|
||||
display(callbacks.banner("PLAY RECAP"))
|
||||
playbook_cb.on_stats(pb.stats)
|
||||
|
||||
for h in hosts:
|
||||
t = pb.stats.summarize(h)
|
||||
if t['failures'] > 0:
|
||||
failed_hosts.append(h)
|
||||
if t['unreachable'] > 0:
|
||||
unreachable_hosts.append(h)
|
||||
|
||||
retries = failed_hosts + unreachable_hosts
|
||||
|
||||
if len(retries) > 0:
|
||||
filename = pb.generate_retry_inventory(retries)
|
||||
if filename:
|
||||
display(" to retry, use: --limit @%s\n" % filename)
|
||||
|
||||
for h in hosts:
|
||||
t = pb.stats.summarize(h)
|
||||
|
||||
display("%s : %s %s %s %s" % (
|
||||
hostcolor(h, t),
|
||||
colorize('ok', t['ok'], 'green'),
|
||||
colorize('changed', t['changed'], 'yellow'),
|
||||
colorize('unreachable', t['unreachable'], 'red'),
|
||||
colorize('failed', t['failures'], 'red')),
|
||||
screen_only=True
|
||||
)
|
||||
|
||||
display("%s : %s %s %s %s" % (
|
||||
hostcolor(h, t, False),
|
||||
colorize('ok', t['ok'], None),
|
||||
colorize('changed', t['changed'], None),
|
||||
colorize('unreachable', t['unreachable'], None),
|
||||
colorize('failed', t['failures'], None)),
|
||||
log_only=True
|
||||
)
|
||||
|
||||
|
||||
print ""
|
||||
if len(failed_hosts) > 0:
|
||||
return 2
|
||||
if len(unreachable_hosts) > 0:
|
||||
return 3
|
||||
|
||||
except errors.AnsibleError, e:
|
||||
display("ERROR: %s" % e, color='red')
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
display(" ", log_only=True)
|
||||
display(" ".join(sys.argv), log_only=True)
|
||||
display(" ", log_only=True)
|
||||
try:
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
except errors.AnsibleError, e:
|
||||
display("ERROR: %s" % e, color='red', stderr=True)
|
||||
sys.exit(1)
|
||||
except KeyboardInterrupt, ke:
|
||||
display("ERROR: interrupted", color='red', stderr=True)
|
||||
sys.exit(1)
|
||||
|
||||
@ -1 +0,0 @@
|
||||
ansible
|
||||
233
bin/ansible-pull
Executable file
233
bin/ansible-pull
Executable file
@ -0,0 +1,233 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# (c) 2012, Stephen Fromm <sfromm@gmail.com>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ansible-pull is a script that runs ansible in local mode
|
||||
# after checking out a playbooks directory from source repo. There is an
|
||||
# example playbook to bootstrap this script in the examples/ dir which
|
||||
# installs ansible and sets it up to run on cron.
|
||||
|
||||
# usage:
|
||||
# ansible-pull -d /var/lib/ansible \
|
||||
# -U http://example.net/content.git [-C production] \
|
||||
# [path/playbook.yml]
|
||||
#
|
||||
# the -d and -U arguments are required; the -C argument is optional.
|
||||
#
|
||||
# ansible-pull accepts an optional argument to specify a playbook
|
||||
# location underneath the workdir and then searches the source repo
|
||||
# for playbooks in the following order, stopping at the first match:
|
||||
#
|
||||
# 1. $workdir/path/playbook.yml, if specified
|
||||
# 2. $workdir/$fqdn.yml
|
||||
# 3. $workdir/$hostname.yml
|
||||
# 4. $workdir/local.yml
|
||||
#
|
||||
# the source repo must contain at least one of these playbooks.
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import datetime
|
||||
import socket
|
||||
import random
|
||||
import time
|
||||
from ansible import utils
|
||||
from ansible.utils import cmd_functions
|
||||
from ansible import errors
|
||||
|
||||
DEFAULT_REPO_TYPE = 'git'
|
||||
DEFAULT_PLAYBOOK = 'local.yml'
|
||||
PLAYBOOK_ERRORS = {1: 'File does not exist',
|
||||
2: 'File is not readable'}
|
||||
|
||||
VERBOSITY=0
|
||||
|
||||
def increment_debug(option, opt, value, parser):
|
||||
global VERBOSITY
|
||||
VERBOSITY += 1
|
||||
|
||||
def try_playbook(path):
|
||||
if not os.path.exists(path):
|
||||
return 1
|
||||
if not os.access(path, os.R_OK):
|
||||
return 2
|
||||
return 0
|
||||
|
||||
|
||||
def select_playbook(path, args):
|
||||
playbook = None
|
||||
if len(args) > 0 and args[0] is not None:
|
||||
playbook = "%s/%s" % (path, args[0])
|
||||
rc = try_playbook(playbook)
|
||||
if rc != 0:
|
||||
print >>sys.stderr, "%s: %s" % (playbook, PLAYBOOK_ERRORS[rc])
|
||||
return None
|
||||
return playbook
|
||||
else:
|
||||
fqdn = socket.getfqdn()
|
||||
hostpb = "%s/%s.yml" % (path, fqdn)
|
||||
shorthostpb = "%s/%s.yml" % (path, fqdn.split('.')[0])
|
||||
localpb = "%s/%s" % (path, DEFAULT_PLAYBOOK)
|
||||
errors = []
|
||||
for pb in [hostpb, shorthostpb, localpb]:
|
||||
rc = try_playbook(pb)
|
||||
if rc == 0:
|
||||
playbook = pb
|
||||
break
|
||||
else:
|
||||
errors.append("%s: %s" % (pb, PLAYBOOK_ERRORS[rc]))
|
||||
if playbook is None:
|
||||
print >>sys.stderr, "\n".join(errors)
|
||||
return playbook
|
||||
|
||||
|
||||
def main(args):
|
||||
""" Set up and run a local playbook """
|
||||
usage = "%prog [options] [playbook.yml]"
|
||||
parser = utils.SortedOptParser(usage=usage)
|
||||
parser.add_option('--purge', default=False, action='store_true',
|
||||
help='purge checkout after playbook run')
|
||||
parser.add_option('-o', '--only-if-changed', dest='ifchanged', default=False, action='store_true',
|
||||
help='only run the playbook if the repository has been updated')
|
||||
parser.add_option('-s', '--sleep', dest='sleep', default=None,
|
||||
help='sleep for random interval (between 0 and n number of seconds) before starting. this is a useful way to disperse git requests')
|
||||
parser.add_option('-f', '--force', dest='force', default=False,
|
||||
action='store_true',
|
||||
help='run the playbook even if the repository could '
|
||||
'not be updated')
|
||||
parser.add_option('-d', '--directory', dest='dest', default=None,
|
||||
help='directory to checkout repository to')
|
||||
#parser.add_option('-l', '--live', default=True, action='store_live',
|
||||
# help='Print the ansible-playbook output while running')
|
||||
parser.add_option('-U', '--url', dest='url', default=None,
|
||||
help='URL of the playbook repository')
|
||||
parser.add_option('-C', '--checkout', dest='checkout',
|
||||
help='branch/tag/commit to checkout. '
|
||||
'Defaults to behavior of repository module.')
|
||||
parser.add_option('-i', '--inventory-file', dest='inventory',
|
||||
help="location of the inventory host file")
|
||||
parser.add_option('-e', '--extra-vars', dest="extra_vars", action="append",
|
||||
help="set additional variables as key=value or YAML/JSON", default=[])
|
||||
parser.add_option('-v', '--verbose', default=False, action="callback",
|
||||
callback=increment_debug,
|
||||
help='Pass -vvvv to ansible-playbook')
|
||||
parser.add_option('-m', '--module-name', dest='module_name',
|
||||
default=DEFAULT_REPO_TYPE,
|
||||
help='Module name used to check out repository. '
|
||||
'Default is %s.' % DEFAULT_REPO_TYPE)
|
||||
parser.add_option('--vault-password-file', dest='vault_password_file',
|
||||
help="vault password file")
|
||||
parser.add_option('-K', '--ask-sudo-pass', default=False, dest='ask_sudo_pass', action='store_true',
|
||||
help='ask for sudo password')
|
||||
options, args = parser.parse_args(args)
|
||||
|
||||
hostname = socket.getfqdn()
|
||||
if not options.dest:
|
||||
# use a hostname dependent directory, in case of $HOME on nfs
|
||||
options.dest = utils.prepare_writeable_dir('~/.ansible/pull/%s' % hostname)
|
||||
|
||||
options.dest = os.path.abspath(options.dest)
|
||||
|
||||
if not options.url:
|
||||
parser.error("URL for repository not specified, use -h for help")
|
||||
return 1
|
||||
|
||||
now = datetime.datetime.now()
|
||||
print >>sys.stderr, now.strftime("Starting ansible-pull at %F %T")
|
||||
|
||||
if not options.inventory:
|
||||
inv_opts = 'localhost,'
|
||||
else:
|
||||
inv_opts = options.inventory
|
||||
limit_opts = 'localhost:%s:127.0.0.1' % hostname
|
||||
repo_opts = "name=%s dest=%s" % (options.url, options.dest)
|
||||
|
||||
if VERBOSITY == 0:
|
||||
base_opts = '-c local --limit "%s"' % limit_opts
|
||||
elif VERBOSITY > 0:
|
||||
debug_level = ''.join([ "v" for x in range(0, VERBOSITY) ])
|
||||
base_opts = '-%s -c local --limit "%s"' % (debug_level, limit_opts)
|
||||
|
||||
if options.checkout:
|
||||
repo_opts += ' version=%s' % options.checkout
|
||||
path = utils.plugins.module_finder.find_plugin(options.module_name)
|
||||
if path is None:
|
||||
sys.stderr.write("module '%s' not found.\n" % options.module_name)
|
||||
return 1
|
||||
cmd = 'ansible all -i "%s" %s -m %s -a "%s"' % (
|
||||
inv_opts, base_opts, options.module_name, repo_opts
|
||||
)
|
||||
|
||||
if options.sleep:
|
||||
try:
|
||||
secs = random.randint(0,int(options.sleep));
|
||||
except ValueError:
|
||||
parser.error("%s is not a number." % options.sleep)
|
||||
return 1
|
||||
|
||||
print >>sys.stderr, "Sleeping for %d seconds..." % secs
|
||||
time.sleep(secs);
|
||||
|
||||
|
||||
# RUN THe CHECKOUT COMMAND
|
||||
rc, out, err = cmd_functions.run_cmd(cmd, live=True)
|
||||
|
||||
if rc != 0:
|
||||
if options.force:
|
||||
print "Unable to update repository. Continuing with (forced) run of playbook."
|
||||
else:
|
||||
return rc
|
||||
elif options.ifchanged and '"changed": true' not in out:
|
||||
print "Repository has not changed, quitting."
|
||||
return 0
|
||||
|
||||
playbook = select_playbook(options.dest, args)
|
||||
|
||||
if playbook is None:
|
||||
print >>sys.stderr, "Could not find a playbook to run."
|
||||
return 1
|
||||
|
||||
cmd = 'ansible-playbook %s %s' % (base_opts, playbook)
|
||||
if options.vault_password_file:
|
||||
cmd += " --vault-password-file=%s" % options.vault_password_file
|
||||
if options.inventory:
|
||||
cmd += ' -i "%s"' % options.inventory
|
||||
for ev in options.extra_vars:
|
||||
cmd += ' -e "%s"' % ev
|
||||
if options.ask_sudo_pass:
|
||||
cmd += ' -K'
|
||||
os.chdir(options.dest)
|
||||
|
||||
# RUN THE PLAYBOOK COMMAND
|
||||
rc, out, err = cmd_functions.run_cmd(cmd, live=True)
|
||||
|
||||
if options.purge:
|
||||
os.chdir('/')
|
||||
try:
|
||||
shutil.rmtree(options.dest)
|
||||
except Exception, e:
|
||||
print >>sys.stderr, "Failed to remove %s: %s" % (options.dest, str(e))
|
||||
|
||||
return rc
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
except KeyboardInterrupt, e:
|
||||
print >>sys.stderr, "Exit on user request.\n"
|
||||
sys.exit(1)
|
||||
@ -1 +0,0 @@
|
||||
ansible
|
||||
217
bin/ansible-vault
Executable file
217
bin/ansible-vault
Executable file
@ -0,0 +1,217 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# (c) 2014, James Tanner <tanner.jc@gmail.com>
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ansible-pull is a script that runs ansible in local mode
|
||||
# after checking out a playbooks directory from source repo. There is an
|
||||
# example playbook to bootstrap this script in the examples/ dir which
|
||||
# installs ansible and sets it up to run on cron.
|
||||
|
||||
#__requires__ = ['ansible']
|
||||
#import pkg_resources
|
||||
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import ansible.constants as C
|
||||
|
||||
from ansible import utils
|
||||
from ansible import errors
|
||||
from ansible.utils.vault import VaultEditor
|
||||
|
||||
from optparse import OptionParser
|
||||
|
||||
#-------------------------------------------------------------------------------------
|
||||
# Utility functions for parsing actions/options
|
||||
#-------------------------------------------------------------------------------------
|
||||
|
||||
VALID_ACTIONS = ("create", "decrypt", "edit", "encrypt", "rekey")
|
||||
|
||||
def build_option_parser(action):
|
||||
"""
|
||||
Builds an option parser object based on the action
|
||||
the user wants to execute.
|
||||
"""
|
||||
|
||||
usage = "usage: %%prog [%s] [--help] [options] file_name" % "|".join(VALID_ACTIONS)
|
||||
epilog = "\nSee '%s <command> --help' for more information on a specific command.\n\n" % os.path.basename(sys.argv[0])
|
||||
OptionParser.format_epilog = lambda self, formatter: self.epilog
|
||||
parser = OptionParser(usage=usage, epilog=epilog)
|
||||
|
||||
if not action:
|
||||
parser.print_help()
|
||||
sys.exit()
|
||||
|
||||
# options for all actions
|
||||
#parser.add_option('-c', '--cipher', dest='cipher', default="AES256", help="cipher to use")
|
||||
parser.add_option('--debug', dest='debug', action="store_true", help="debug")
|
||||
parser.add_option('--vault-password-file', dest='password_file',
|
||||
help="vault password file", default=C.DEFAULT_VAULT_PASSWORD_FILE)
|
||||
|
||||
# options specific to actions
|
||||
if action == "create":
|
||||
parser.set_usage("usage: %prog create [options] file_name")
|
||||
elif action == "decrypt":
|
||||
parser.set_usage("usage: %prog decrypt [options] file_name")
|
||||
elif action == "edit":
|
||||
parser.set_usage("usage: %prog edit [options] file_name")
|
||||
elif action == "encrypt":
|
||||
parser.set_usage("usage: %prog encrypt [options] file_name")
|
||||
elif action == "rekey":
|
||||
parser.set_usage("usage: %prog rekey [options] file_name")
|
||||
|
||||
# done, return the parser
|
||||
return parser
|
||||
|
||||
def get_action(args):
|
||||
"""
|
||||
Get the action the user wants to execute from the
|
||||
sys argv list.
|
||||
"""
|
||||
for i in range(0,len(args)):
|
||||
arg = args[i]
|
||||
if arg in VALID_ACTIONS:
|
||||
del args[i]
|
||||
return arg
|
||||
return None
|
||||
|
||||
def get_opt(options, k, defval=""):
|
||||
"""
|
||||
Returns an option from an Optparse values instance.
|
||||
"""
|
||||
try:
|
||||
data = getattr(options, k)
|
||||
except:
|
||||
return defval
|
||||
if k == "roles_path":
|
||||
if os.pathsep in data:
|
||||
data = data.split(os.pathsep)[0]
|
||||
return data
|
||||
|
||||
#-------------------------------------------------------------------------------------
|
||||
# Command functions
|
||||
#-------------------------------------------------------------------------------------
|
||||
|
||||
def execute_create(args, options, parser):
|
||||
if len(args) > 1:
|
||||
raise errors.AnsibleError("'create' does not accept more than one filename")
|
||||
|
||||
if not options.password_file:
|
||||
password, new_password = utils.ask_vault_passwords(ask_vault_pass=True, confirm_vault=True)
|
||||
else:
|
||||
password = utils.read_vault_file(options.password_file)
|
||||
|
||||
cipher = 'AES256'
|
||||
if hasattr(options, 'cipher'):
|
||||
cipher = options.cipher
|
||||
|
||||
this_editor = VaultEditor(cipher, password, args[0])
|
||||
this_editor.create_file()
|
||||
|
||||
def execute_decrypt(args, options, parser):
|
||||
|
||||
if not options.password_file:
|
||||
password, new_password = utils.ask_vault_passwords(ask_vault_pass=True)
|
||||
else:
|
||||
password = utils.read_vault_file(options.vault_password_file)
|
||||
|
||||
cipher = 'AES256'
|
||||
if hasattr(options, 'cipher'):
|
||||
cipher = options.cipher
|
||||
|
||||
for f in args:
|
||||
this_editor = VaultEditor(cipher, password, f)
|
||||
this_editor.decrypt_file()
|
||||
|
||||
print "Decryption successful"
|
||||
|
||||
def execute_edit(args, options, parser):
|
||||
|
||||
if len(args) > 1:
|
||||
raise errors.AnsibleError("create does not accept more than one filename")
|
||||
|
||||
if not options.password_file:
|
||||
password, new_password = utils.ask_vault_passwords(ask_vault_pass=True)
|
||||
else:
|
||||
password = utils.read_vault_file(options.password_file)
|
||||
|
||||
cipher = None
|
||||
|
||||
for f in args:
|
||||
this_editor = VaultEditor(cipher, password, f)
|
||||
this_editor.edit_file()
|
||||
|
||||
def execute_encrypt(args, options, parser):
|
||||
|
||||
if not options.password_file:
|
||||
password, new_password = utils.ask_vault_passwords(ask_vault_pass=True, confirm_vault=True)
|
||||
else:
|
||||
password = utils.read_vault_file(options.password_file)
|
||||
|
||||
cipher = 'AES256'
|
||||
if hasattr(options, 'cipher'):
|
||||
cipher = options.cipher
|
||||
|
||||
for f in args:
|
||||
this_editor = VaultEditor(cipher, password, f)
|
||||
this_editor.encrypt_file()
|
||||
|
||||
print "Encryption successful"
|
||||
|
||||
def execute_rekey(args, options, parser):
|
||||
|
||||
if not options.password_file:
|
||||
password, __ = utils.ask_vault_passwords(ask_vault_pass=True)
|
||||
else:
|
||||
password = utils.read_vault_file(options.password_file)
|
||||
|
||||
__, new_password = utils.ask_vault_passwords(ask_vault_pass=False, ask_new_vault_pass=True, confirm_new=True)
|
||||
|
||||
cipher = None
|
||||
for f in args:
|
||||
this_editor = VaultEditor(cipher, password, f)
|
||||
this_editor.rekey_file(new_password)
|
||||
|
||||
print "Rekey successful"
|
||||
|
||||
#-------------------------------------------------------------------------------------
|
||||
# MAIN
|
||||
#-------------------------------------------------------------------------------------
|
||||
|
||||
def main():
|
||||
|
||||
action = get_action(sys.argv)
|
||||
parser = build_option_parser(action)
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if not len(args):
|
||||
raise errors.AnsibleError(
|
||||
"The '%s' command requires a filename as the first argument" % action
|
||||
)
|
||||
|
||||
# execute the desired action
|
||||
try:
|
||||
fn = globals()["execute_%s" % action]
|
||||
fn(args, options, parser)
|
||||
except Exception, err:
|
||||
if options.debug:
|
||||
print traceback.format_exc()
|
||||
print "ERROR:",err
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,17 +0,0 @@
|
||||
inventory
|
||||
=========
|
||||
|
||||
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.
|
||||
|
||||
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 plugins of your own. The sky is the limit!
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
# Ansible external inventory script settings for Abiquo
|
||||
#
|
||||
|
||||
# Define an Abiquo user with access to Abiquo API which will be used to
|
||||
# perform required queries to obtain information to generate the Ansible
|
||||
# inventory output.
|
||||
#
|
||||
[auth]
|
||||
apiuser = admin
|
||||
apipass = xabiquo
|
||||
|
||||
|
||||
# Specify Abiquo API version in major.minor format and the access URI to
|
||||
# API endpoint. Tested versions are: 2.6 , 3.0 and 3.1
|
||||
# To confirm that your box haves access to Abiquo API you can perform a
|
||||
# curl command, replacing with suitable values, similar to this:
|
||||
# curl -X GET https://192.168.2.100/api/login -u admin:xabiquo
|
||||
#
|
||||
[api]
|
||||
version = 3.0
|
||||
uri = https://192.168.2.100/api
|
||||
# You probably won't need to modify login preferences, but just in case
|
||||
login_path = /login
|
||||
login_type = application/vnd.abiquo.user+json
|
||||
|
||||
|
||||
# To avoid performing excessive calls to Abiquo API you can define a
|
||||
# cache for the plugin output. Within the time defined in seconds, latest
|
||||
# output will be reused. After that time, the cache will be refreshed.
|
||||
#
|
||||
[cache]
|
||||
cache_max_age = 30
|
||||
cache_dir = /tmp
|
||||
|
||||
|
||||
[defaults]
|
||||
# Depending in your Abiquo environment, you may want to use only public IP
|
||||
# addresses (if using public cloud providers) or also private IP addresses.
|
||||
# You can set this with public_ip_only configuration.
|
||||
public_ip_only = false
|
||||
# default_net_interface only is used if public_ip_only = false
|
||||
# If public_ip_only is set to false, you can choose default nic to obtain
|
||||
# IP address to define the host.
|
||||
default_net_interface = nic0
|
||||
# Only deployed VM are displayed in the plugin output.
|
||||
deployed_only = true
|
||||
# Define if VM metadata is obtained from Abiquo API.
|
||||
get_metadata = false
|
||||
@ -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
|
||||
@ -1,802 +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
|
||||
|
||||
Environment variables:
|
||||
- AZURE_PROFILE
|
||||
- AZURE_CLIENT_ID
|
||||
- AZURE_SECRET
|
||||
- AZURE_SUBSCRIPTION_ID
|
||||
- AZURE_TENANT
|
||||
- AZURE_AD_USER
|
||||
- AZURE_PASSWORD
|
||||
|
||||
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 ConfigParser
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from packaging.version import Version
|
||||
|
||||
from os.path import expanduser
|
||||
|
||||
HAS_AZURE = True
|
||||
HAS_AZURE_EXC = None
|
||||
|
||||
try:
|
||||
from msrestazure.azure_exceptions import CloudError
|
||||
from azure.mgmt.compute import __version__ as azure_compute_version
|
||||
from azure.common import AzureMissingResourceHttpError, AzureHttpError
|
||||
from azure.common.credentials import ServicePrincipalCredentials, UserPassCredentials
|
||||
from azure.mgmt.network.network_management_client import NetworkManagementClient
|
||||
from azure.mgmt.resource.resources.resource_management_client import ResourceManagementClient
|
||||
from azure.mgmt.compute.compute_management_client import ComputeManagementClient
|
||||
except ImportError as exc:
|
||||
HAS_AZURE_EXC = exc
|
||||
HAS_AZURE = False
|
||||
|
||||
|
||||
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'
|
||||
)
|
||||
|
||||
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 = "0.30.0rc5"
|
||||
|
||||
|
||||
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._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 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'])
|
||||
elif self.credentials.get('ad_user') is not None and self.credentials.get('password') is not None:
|
||||
self.azure_credentials = UserPassCredentials(self.credentials['ad_user'], self.credentials['password'])
|
||||
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 = ConfigParser.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
|
||||
|
||||
# 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)
|
||||
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)
|
||||
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)
|
||||
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('--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,
|
||||
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.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 = ConfigParser.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>=2.0.0rc5' --upgrade`) - {0}".format(HAS_AZURE_EXC))
|
||||
|
||||
if Version(azure_compute_version) < Version(AZURE_MIN_VERSION):
|
||||
sys.exit("Expecting azure.mgmt.compute.__version__ to be {0}. Found version {1} "
|
||||
"Do you have Azure >= 2.0.0rc5 installed? (try `pip install 'azure>=2.0.0rc5' --upgrade`)".format(AZURE_MIN_VERSION, azure_compute_version))
|
||||
|
||||
AzureInventory()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -1,40 +0,0 @@
|
||||
#!/usr/bin/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/>.
|
||||
|
||||
# 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 =
|
||||
@ -1,261 +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_name: str
|
||||
- brook_description: str
|
||||
- brook_project: str
|
||||
- brook_template: str
|
||||
- brook_region: 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.1
|
||||
"""
|
||||
|
||||
|
||||
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:
|
||||
print('You must provide (at least) your Brook.io API token to generate the dynamic '
|
||||
'inventory.')
|
||||
sys.exit(1)
|
||||
|
||||
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
|
||||
template = templates_api.show_template(template_id=instance.template)
|
||||
|
||||
# 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.keys():
|
||||
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.keys():
|
||||
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.keys():
|
||||
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_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_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
|
||||
|
||||
# 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))
|
||||
@ -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
|
||||
@ -1,461 +0,0 @@
|
||||
#!/usr/bin/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()
|
||||
@ -1,5 +0,0 @@
|
||||
[cloudstack]
|
||||
#endpoint = https://api.exoscale.ch/compute
|
||||
endpoint = https://cloud.example.com/client/api
|
||||
key = cloudstack api key
|
||||
secret = cloudstack api secret
|
||||
@ -1,236 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# (c) 2015, René Moser <mail@renemoser.net>
|
||||
#
|
||||
# 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 CloudStack external inventory script.
|
||||
=============================================
|
||||
|
||||
Generates Ansible inventory from CloudStack. Configuration is read from
|
||||
'cloudstack.ini'. If you need to pass the project, write a simple wrapper
|
||||
script, e.g. project_cloudstack.sh:
|
||||
|
||||
#!/bin/bash
|
||||
cloudstack.py --project <your_project> $@
|
||||
|
||||
|
||||
When run against a specific host, this script returns the following attributes
|
||||
based on the data obtained from CloudStack API:
|
||||
|
||||
"web01": {
|
||||
"cpu_number": 2,
|
||||
"nic": [
|
||||
{
|
||||
"ip": "10.102.76.98",
|
||||
"mac": "02:00:50:99:00:01",
|
||||
"type": "Isolated",
|
||||
"netmask": "255.255.255.0",
|
||||
"gateway": "10.102.76.1"
|
||||
},
|
||||
{
|
||||
"ip": "10.102.138.63",
|
||||
"mac": "06:b7:5a:00:14:84",
|
||||
"type": "Shared",
|
||||
"netmask": "255.255.255.0",
|
||||
"gateway": "10.102.138.1"
|
||||
}
|
||||
],
|
||||
"default_ip": "10.102.76.98",
|
||||
"zone": "ZUERICH",
|
||||
"created": "2014-07-02T07:53:50+0200",
|
||||
"hypervisor": "VMware",
|
||||
"memory": 2048,
|
||||
"state": "Running",
|
||||
"tags": [],
|
||||
"cpu_speed": 1800,
|
||||
"affinity_group": [],
|
||||
"service_offering": "Small",
|
||||
"cpu_used": "62%"
|
||||
}
|
||||
|
||||
|
||||
usage: cloudstack.py [--list] [--host HOST] [--project PROJECT]
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
try:
|
||||
import json
|
||||
except:
|
||||
import simplejson as json
|
||||
|
||||
|
||||
try:
|
||||
from cs import CloudStack, CloudStackException, read_config
|
||||
except ImportError:
|
||||
print("Error: CloudStack library must be installed: pip install cs.",
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class CloudStackInventory(object):
|
||||
def __init__(self):
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--host')
|
||||
parser.add_argument('--list', action='store_true')
|
||||
parser.add_argument('--project')
|
||||
|
||||
options = parser.parse_args()
|
||||
try:
|
||||
self.cs = CloudStack(**read_config())
|
||||
except CloudStackException as e:
|
||||
print("Error: Could not connect to CloudStack API", file=sys.stderr)
|
||||
|
||||
project_id = ''
|
||||
if options.project:
|
||||
project_id = self.get_project_id(options.project)
|
||||
|
||||
if options.host:
|
||||
data = self.get_host(options.host, project_id)
|
||||
print(json.dumps(data, indent=2))
|
||||
|
||||
elif options.list:
|
||||
data = self.get_list(project_id)
|
||||
print(json.dumps(data, indent=2))
|
||||
else:
|
||||
print("usage: --list | --host <hostname> [--project <project>]",
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
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:
|
||||
return p['id']
|
||||
print("Error: Project %s not found." % project, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_host(self, name, project_id=''):
|
||||
hosts = self.cs.listVirtualMachines(projectid=project_id)
|
||||
data = {}
|
||||
if not hosts:
|
||||
return data
|
||||
for host in hosts['virtualmachine']:
|
||||
host_name = host['displayname']
|
||||
if name == host_name:
|
||||
data['zone'] = host['zonename']
|
||||
if 'group' in host:
|
||||
data['group'] = host['group']
|
||||
data['state'] = host['state']
|
||||
data['service_offering'] = host['serviceofferingname']
|
||||
data['affinity_group'] = host['affinitygroup']
|
||||
data['security_group'] = host['securitygroup']
|
||||
data['cpu_number'] = host['cpunumber']
|
||||
data['cpu_speed'] = host['cpuspeed']
|
||||
if 'cpuused' in host:
|
||||
data['cpu_used'] = host['cpuused']
|
||||
data['memory'] = host['memory']
|
||||
data['tags'] = host['tags']
|
||||
data['hypervisor'] = host['hypervisor']
|
||||
data['created'] = host['created']
|
||||
data['nic'] = []
|
||||
for nic in host['nic']:
|
||||
data['nic'].append({
|
||||
'ip': nic['ipaddress'],
|
||||
'mac': nic['macaddress'],
|
||||
'netmask': nic['netmask'],
|
||||
'gateway': nic['gateway'],
|
||||
'type': nic['type'],
|
||||
})
|
||||
if nic['isdefault']:
|
||||
data['default_ip'] = nic['ipaddress']
|
||||
break;
|
||||
return data
|
||||
|
||||
|
||||
def get_list(self, project_id=''):
|
||||
data = {
|
||||
'all': {
|
||||
'hosts': [],
|
||||
},
|
||||
'_meta': {
|
||||
'hostvars': {},
|
||||
},
|
||||
}
|
||||
|
||||
groups = self.cs.listInstanceGroups(projectid=project_id)
|
||||
if groups:
|
||||
for group in groups['instancegroup']:
|
||||
group_name = group['name']
|
||||
if group_name and not group_name in data:
|
||||
data[group_name] = {
|
||||
'hosts': []
|
||||
}
|
||||
|
||||
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] = {}
|
||||
data['_meta']['hostvars'][host_name]['zone'] = host['zonename']
|
||||
if 'group' in host:
|
||||
data['_meta']['hostvars'][host_name]['group'] = host['group']
|
||||
data['_meta']['hostvars'][host_name]['state'] = host['state']
|
||||
data['_meta']['hostvars'][host_name]['service_offering'] = host['serviceofferingname']
|
||||
data['_meta']['hostvars'][host_name]['affinity_group'] = host['affinitygroup']
|
||||
data['_meta']['hostvars'][host_name]['security_group'] = host['securitygroup']
|
||||
data['_meta']['hostvars'][host_name]['cpu_number'] = host['cpunumber']
|
||||
data['_meta']['hostvars'][host_name]['cpu_speed'] = host['cpuspeed']
|
||||
if 'cpuused' in host:
|
||||
data['_meta']['hostvars'][host_name]['cpu_used'] = host['cpuused']
|
||||
data['_meta']['hostvars'][host_name]['created'] = host['created']
|
||||
data['_meta']['hostvars'][host_name]['memory'] = host['memory']
|
||||
data['_meta']['hostvars'][host_name]['tags'] = host['tags']
|
||||
data['_meta']['hostvars'][host_name]['hypervisor'] = host['hypervisor']
|
||||
data['_meta']['hostvars'][host_name]['created'] = host['created']
|
||||
data['_meta']['hostvars'][host_name]['nic'] = []
|
||||
for nic in host['nic']:
|
||||
data['_meta']['hostvars'][host_name]['nic'].append({
|
||||
'ip': nic['ipaddress'],
|
||||
'mac': nic['macaddress'],
|
||||
'netmask': nic['netmask'],
|
||||
'gateway': nic['gateway'],
|
||||
'type': nic['type'],
|
||||
})
|
||||
if nic['isdefault']:
|
||||
data['_meta']['hostvars'][host_name]['default_ip'] = nic['ipaddress']
|
||||
|
||||
group_name = ''
|
||||
if 'group' in host:
|
||||
group_name = host['group']
|
||||
|
||||
if group_name and group_name in data:
|
||||
data[group_name]['hosts'].append(host_name)
|
||||
return data
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
CloudStackInventory()
|
||||
@ -1,37 +0,0 @@
|
||||
# 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
|
||||
@ -1,483 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
#
|
||||
# (c) 2015, Steve Gargan <steve.gargan@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/>.
|
||||
|
||||
######################################################################
|
||||
|
||||
'''
|
||||
Consul.io inventory script (http://consul.io)
|
||||
======================================
|
||||
|
||||
Generates Ansible inventory from nodes in a Consul cluster. This script will
|
||||
group nodes by:
|
||||
- datacenter,
|
||||
- registered service
|
||||
- service tags
|
||||
- service status
|
||||
- values from the k/v store
|
||||
|
||||
This script can be run with the switches
|
||||
--list as expected groups all the nodes in all datacenters
|
||||
--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.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.
|
||||
|
||||
http://www.consul.io/docs/agent/options.html
|
||||
|
||||
Other options include:
|
||||
|
||||
'datacenter':
|
||||
|
||||
which restricts the included nodes to those from the given datacenter
|
||||
|
||||
'domain':
|
||||
|
||||
if specified then the inventory will generate domain names that will resolve
|
||||
via Consul's inbuilt DNS. The name is derived from the node name, datacenter
|
||||
and domain <node_name>.node.<datacenter>.<domain>. Note that you will need to
|
||||
have consul hooked into your DNS server for these to resolve. See the consul
|
||||
DNS docs for more info.
|
||||
|
||||
which restricts the included nodes to those from the given datacenter
|
||||
|
||||
'servers_suffix':
|
||||
|
||||
defining the a suffix to add to the service name when creating the service
|
||||
group. e.g Service name of 'redis' and a suffix of '_servers' will add
|
||||
each nodes address to the group name 'redis_servers'. No suffix is added
|
||||
if this is not set
|
||||
|
||||
'tags':
|
||||
|
||||
boolean flag defining if service tags should be used to create Inventory
|
||||
groups e.g. an nginx service with the tags ['master', 'v1'] will create
|
||||
groups nginx_master and nginx_v1 to which the node running the service
|
||||
will be added. No tag groups are created if this is missing.
|
||||
|
||||
'token':
|
||||
|
||||
ACL token to use to authorize access to the key value store. May be required
|
||||
to retrieve the kv_groups and kv_metadata based on your consul configuration.
|
||||
|
||||
'kv_groups':
|
||||
|
||||
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
|
||||
returned 'test,honeypot' then the node address to both groups.
|
||||
|
||||
'kv_metadata':
|
||||
|
||||
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/groups/nyc-dc1/nyc-web-1' should contain '{"databse": "postgres"}'
|
||||
|
||||
'availability':
|
||||
|
||||
if true then availability groups will be created for each service. The node will
|
||||
be added to one of the groups based on the health status of the service. The
|
||||
group name is derived from the service name and the configurable availability
|
||||
suffixes
|
||||
|
||||
'available_suffix':
|
||||
|
||||
suffix that should be appended to the service availability groups for available
|
||||
services e.g. if the suffix is '_up' and the service is nginx, then nodes with
|
||||
healthy nginx services will be added to the nginix_up group. Defaults to
|
||||
'_available'
|
||||
|
||||
'unavailable_suffix':
|
||||
|
||||
as above but for unhealthy services, defaults to '_unavailable'
|
||||
|
||||
Note that if the inventory discovers an 'ssh' service running on a node it will
|
||||
register the port as ansible_ssh_port in the node's metadata and this port will
|
||||
be used to access the machine.
|
||||
```
|
||||
|
||||
'''
|
||||
|
||||
import os
|
||||
import re
|
||||
import argparse
|
||||
import sys
|
||||
from time import time
|
||||
import sys
|
||||
import ConfigParser
|
||||
import urllib, urllib2, base64
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
try:
|
||||
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'""")
|
||||
|
||||
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
|
||||
|
||||
config = ConsulConfig()
|
||||
self.config = config
|
||||
|
||||
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()
|
||||
|
||||
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_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(
|
||||
'unavailable_suffix', '_unavailable')
|
||||
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'''
|
||||
|
||||
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'])
|
||||
|
||||
self.load_groups_from_kv(node_data)
|
||||
self.load_node_metadata_from_kv(node_data)
|
||||
self.load_availability_groups(node_data, datacenter)
|
||||
|
||||
for name, service in node_data['Services'].items():
|
||||
self.load_data_from_service(name, service, node_data)
|
||||
|
||||
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_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_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.is_service("ssh", service_name):
|
||||
self.add_metadata(node_data, "ansible_ssh_port", service['Port'])
|
||||
|
||||
if self.config.has_config('servers_suffix'):
|
||||
service_name = service_name + self.config.servers_suffix
|
||||
|
||||
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 is_service(self, target, name):
|
||||
return name and (name.lower() == target.lower())
|
||||
|
||||
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 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 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'])
|
||||
|
||||
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 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 add_node_to_map(self, map, name, node):
|
||||
self.push(map, name, self.get_inventory_name(node))
|
||||
|
||||
|
||||
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 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):
|
||||
|
||||
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 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.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)
|
||||
|
||||
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')
|
||||
|
||||
args = parser.parse_args()
|
||||
arg_names = ['host', 'datacenter']
|
||||
|
||||
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_consul_api(self):
|
||||
'''get an instance of the api based on the supplied configuration'''
|
||||
host = 'localhost'
|
||||
port = 8500
|
||||
token = None
|
||||
|
||||
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()
|
||||
@ -1,873 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# (c) 2016 Paul Durivage <paul.durivage@gmail.com>
|
||||
# Chris Houseknecht <house@redhat.com>
|
||||
# James Tanner <jtanner@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/>.
|
||||
#
|
||||
|
||||
DOCUMENTATION = '''
|
||||
|
||||
Docker Inventory Script
|
||||
=======================
|
||||
The inventory script generates dynamic inventory by making API requests to one or more Docker APIs. It's dynamic
|
||||
because the inventory is generated at run-time rather than being read from a static file. The script generates the
|
||||
inventory by connecting to one or many Docker APIs and inspecting the containers it finds at each API. Which APIs the
|
||||
script contacts can be defined using environment variables or a configuration file.
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
Using the docker modules requires having docker-py <https://docker-py.readthedocs.org/en/stable/>
|
||||
installed on the host running Ansible. To install docker-py:
|
||||
|
||||
pip install docker-py
|
||||
|
||||
|
||||
Run for Specific Host
|
||||
---------------------
|
||||
When run for a specific container using the --host option this script returns the following hostvars:
|
||||
|
||||
{
|
||||
"ansible_ssh_host": "",
|
||||
"ansible_ssh_port": 0,
|
||||
"docker_apparmorprofile": "",
|
||||
"docker_args": [],
|
||||
"docker_config": {
|
||||
"AttachStderr": false,
|
||||
"AttachStdin": false,
|
||||
"AttachStdout": false,
|
||||
"Cmd": [
|
||||
"/hello"
|
||||
],
|
||||
"Domainname": "",
|
||||
"Entrypoint": null,
|
||||
"Env": null,
|
||||
"Hostname": "9f2f80b0a702",
|
||||
"Image": "hello-world",
|
||||
"Labels": {},
|
||||
"OnBuild": null,
|
||||
"OpenStdin": false,
|
||||
"StdinOnce": false,
|
||||
"Tty": false,
|
||||
"User": "",
|
||||
"Volumes": null,
|
||||
"WorkingDir": ""
|
||||
},
|
||||
"docker_created": "2016-04-18T02:05:59.659599249Z",
|
||||
"docker_driver": "aufs",
|
||||
"docker_execdriver": "native-0.2",
|
||||
"docker_execids": null,
|
||||
"docker_graphdriver": {
|
||||
"Data": null,
|
||||
"Name": "aufs"
|
||||
},
|
||||
"docker_hostconfig": {
|
||||
"Binds": null,
|
||||
"BlkioWeight": 0,
|
||||
"CapAdd": null,
|
||||
"CapDrop": null,
|
||||
"CgroupParent": "",
|
||||
"ConsoleSize": [
|
||||
0,
|
||||
0
|
||||
],
|
||||
"ContainerIDFile": "",
|
||||
"CpuPeriod": 0,
|
||||
"CpuQuota": 0,
|
||||
"CpuShares": 0,
|
||||
"CpusetCpus": "",
|
||||
"CpusetMems": "",
|
||||
"Devices": null,
|
||||
"Dns": null,
|
||||
"DnsOptions": null,
|
||||
"DnsSearch": null,
|
||||
"ExtraHosts": null,
|
||||
"GroupAdd": null,
|
||||
"IpcMode": "",
|
||||
"KernelMemory": 0,
|
||||
"Links": null,
|
||||
"LogConfig": {
|
||||
"Config": {},
|
||||
"Type": "json-file"
|
||||
},
|
||||
"LxcConf": null,
|
||||
"Memory": 0,
|
||||
"MemoryReservation": 0,
|
||||
"MemorySwap": 0,
|
||||
"MemorySwappiness": null,
|
||||
"NetworkMode": "default",
|
||||
"OomKillDisable": false,
|
||||
"PidMode": "host",
|
||||
"PortBindings": null,
|
||||
"Privileged": false,
|
||||
"PublishAllPorts": false,
|
||||
"ReadonlyRootfs": false,
|
||||
"RestartPolicy": {
|
||||
"MaximumRetryCount": 0,
|
||||
"Name": ""
|
||||
},
|
||||
"SecurityOpt": [
|
||||
"label:disable"
|
||||
],
|
||||
"UTSMode": "",
|
||||
"Ulimits": null,
|
||||
"VolumeDriver": "",
|
||||
"VolumesFrom": null
|
||||
},
|
||||
"docker_hostnamepath": "/mnt/sda1/var/lib/docker/containers/9f2f80b0a702361d1ac432e6af816c19bda46da15c21264fb418c873de635a14/hostname",
|
||||
"docker_hostspath": "/mnt/sda1/var/lib/docker/containers/9f2f80b0a702361d1ac432e6af816c19bda46da15c21264fb418c873de635a14/hosts",
|
||||
"docker_id": "9f2f80b0a702361d1ac432e6af816c19bda46da15c21264fb418c873de635a14",
|
||||
"docker_image": "0a6ba66e537a53a5ea94f7c6a99c534c6adb12e3ed09326d4bf3b38f7c3ba4e7",
|
||||
"docker_logpath": "/mnt/sda1/var/lib/docker/containers/9f2f80b0a702361d1ac432e6af816c19bda46da15c21264fb418c873de635a14/9f2f80b0a702361d1ac432e6af816c19bda46da15c21264fb418c873de635a14-json.log",
|
||||
"docker_mountlabel": "",
|
||||
"docker_mounts": [],
|
||||
"docker_name": "/hello-world",
|
||||
"docker_networksettings": {
|
||||
"Bridge": "",
|
||||
"EndpointID": "",
|
||||
"Gateway": "",
|
||||
"GlobalIPv6Address": "",
|
||||
"GlobalIPv6PrefixLen": 0,
|
||||
"HairpinMode": false,
|
||||
"IPAddress": "",
|
||||
"IPPrefixLen": 0,
|
||||
"IPv6Gateway": "",
|
||||
"LinkLocalIPv6Address": "",
|
||||
"LinkLocalIPv6PrefixLen": 0,
|
||||
"MacAddress": "",
|
||||
"Networks": {
|
||||
"bridge": {
|
||||
"EndpointID": "",
|
||||
"Gateway": "",
|
||||
"GlobalIPv6Address": "",
|
||||
"GlobalIPv6PrefixLen": 0,
|
||||
"IPAddress": "",
|
||||
"IPPrefixLen": 0,
|
||||
"IPv6Gateway": "",
|
||||
"MacAddress": ""
|
||||
}
|
||||
},
|
||||
"Ports": null,
|
||||
"SandboxID": "",
|
||||
"SandboxKey": "",
|
||||
"SecondaryIPAddresses": null,
|
||||
"SecondaryIPv6Addresses": null
|
||||
},
|
||||
"docker_path": "/hello",
|
||||
"docker_processlabel": "",
|
||||
"docker_resolvconfpath": "/mnt/sda1/var/lib/docker/containers/9f2f80b0a702361d1ac432e6af816c19bda46da15c21264fb418c873de635a14/resolv.conf",
|
||||
"docker_restartcount": 0,
|
||||
"docker_short_id": "9f2f80b0a7023",
|
||||
"docker_state": {
|
||||
"Dead": false,
|
||||
"Error": "",
|
||||
"ExitCode": 0,
|
||||
"FinishedAt": "2016-04-18T02:06:00.296619369Z",
|
||||
"OOMKilled": false,
|
||||
"Paused": false,
|
||||
"Pid": 0,
|
||||
"Restarting": false,
|
||||
"Running": false,
|
||||
"StartedAt": "2016-04-18T02:06:00.272065041Z",
|
||||
"Status": "exited"
|
||||
}
|
||||
}
|
||||
|
||||
Groups
|
||||
------
|
||||
When run in --list mode (the default), container instances are grouped by:
|
||||
|
||||
- container id
|
||||
- container name
|
||||
- container short id
|
||||
- image_name (image_<image name>)
|
||||
- docker_host
|
||||
- running
|
||||
- stopped
|
||||
|
||||
|
||||
Configuration:
|
||||
--------------
|
||||
You can control the behavior of the inventory script by passing arguments, defining environment variables, or
|
||||
creating a configuration file named docker.yml (sample provided in ansible/contrib/inventory). The order of precedence
|
||||
is command line args, then the docker.yml file and finally environment variables.
|
||||
|
||||
Environment variables:
|
||||
......................
|
||||
|
||||
To connect to a single Docker API the following variables can be defined in the environment to control the connection
|
||||
options. These are the same environment variables used by the Docker modules.
|
||||
|
||||
DOCKER_HOST
|
||||
The URL or Unix socket path used to connect to the Docker API. Defaults to unix://var/run/docker.sock.
|
||||
|
||||
DOCKER_API_VERSION:
|
||||
The version of the Docker API running on the Docker Host. Defaults to the latest version of the API supported
|
||||
by docker-py.
|
||||
|
||||
DOCKER_TIMEOUT:
|
||||
The maximum amount of time in seconds to wait on a response fromm the API. Defaults to 60 seconds.
|
||||
|
||||
DOCKER_TLS:
|
||||
Secure the connection to the API by using TLS without verifying the authenticity of the Docker host server.
|
||||
Defaults to False.
|
||||
|
||||
DOCKER_TLS_VERIFY:
|
||||
Secure the connection to the API by using TLS and verifying the authenticity of the Docker host server.
|
||||
Default is False
|
||||
|
||||
DOCKER_TLS_HOSTNAME:
|
||||
When verifying the authenticity of the Docker Host server, provide the expected name of the server. Defaults
|
||||
to localhost.
|
||||
|
||||
DOCKER_CERT_PATH:
|
||||
Path to the directory containing the client certificate, client key and CA certificate.
|
||||
|
||||
DOCKER_SSL_VERSION:
|
||||
Provide a valid SSL version number. Default value determined by docker-py, which at the time of this writing
|
||||
was 1.0
|
||||
|
||||
In addition to the connection variables there are a couple variables used to control the execution and output of the
|
||||
script:
|
||||
|
||||
DOCKER_CONFIG_FILE
|
||||
Path to the configuration file. Defaults to ./docker.yml.
|
||||
|
||||
DOCKER_PRIVATE_SSH_PORT:
|
||||
The private port (container port) on which SSH is listening for connections. Defaults to 22.
|
||||
|
||||
DOCKER_DEFAULT_IP:
|
||||
The IP address to assign to ansible_host when the container's SSH port is mapped to interface '0.0.0.0'.
|
||||
|
||||
|
||||
Configuration File
|
||||
..................
|
||||
|
||||
Using a configuration file provides a means for defining a set of Docker APIs from which to build an inventory.
|
||||
|
||||
The default name of the file is derived from the name of the inventory script. By default the script will look for
|
||||
basename of the script (i.e. docker) with an extension of '.yml'.
|
||||
|
||||
You can also override the default name of the script by defining DOCKER_CONFIG_FILE in the environment.
|
||||
|
||||
Here's what you can define in docker_inventory.yml:
|
||||
|
||||
defaults
|
||||
Defines a default connection. Defaults will be taken from this and applied to any values not provided
|
||||
for a host defined in the hosts list.
|
||||
|
||||
hosts
|
||||
If you wish to get inventory from more than one Docker host, define a hosts list.
|
||||
|
||||
For the default host and each host in the hosts list define the following attributes:
|
||||
|
||||
host:
|
||||
description: The URL or Unix socket path used to connect to the Docker API.
|
||||
required: yes
|
||||
|
||||
tls:
|
||||
description: Connect using TLS without verifying the authenticity of the Docker host server.
|
||||
default: false
|
||||
required: false
|
||||
|
||||
tls_verify:
|
||||
description: Connect using TLS without verifying the authenticity of the Docker host server.
|
||||
default: false
|
||||
required: false
|
||||
|
||||
cert_path:
|
||||
description: Path to the client's TLS certificate file.
|
||||
default: null
|
||||
required: false
|
||||
|
||||
cacert_path:
|
||||
description: Use a CA certificate when performing server verification by providing the path to a CA certificate file.
|
||||
default: null
|
||||
required: false
|
||||
|
||||
key_path:
|
||||
description: Path to the client's TLS key file.
|
||||
default: null
|
||||
required: false
|
||||
|
||||
version:
|
||||
description: The Docker API version.
|
||||
required: false
|
||||
default: will be supplied by the docker-py module.
|
||||
|
||||
timeout:
|
||||
description: The amount of time in seconds to wait on an API response.
|
||||
required: false
|
||||
default: 60
|
||||
|
||||
default_ip:
|
||||
description: The IP address to assign to ansible_host when the container's SSH port is mapped to interface
|
||||
'0.0.0.0'.
|
||||
required: false
|
||||
default: 127.0.0.1
|
||||
|
||||
private_ssh_port:
|
||||
description: The port containers use for SSH
|
||||
required: false
|
||||
default: 22
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
# Connect to the Docker API on localhost port 4243 and format the JSON output
|
||||
DOCKER_HOST=tcp://localhost:4243 ./docker.py --pretty
|
||||
|
||||
# Any container's ssh port exposed on 0.0.0.0 will be mapped to
|
||||
# another IP address (where Ansible will attempt to connect via SSH)
|
||||
DOCKER_DEFAULT_IP=1.2.3.4 ./docker.py --pretty
|
||||
|
||||
# Run as input to a playbook:
|
||||
ansible-playbook -i ~/projects/ansible/contrib/inventory/docker.py docker_inventory_test.yml
|
||||
|
||||
# Simple playbook to invoke with the above example:
|
||||
|
||||
- name: Test docker_inventory
|
||||
hosts: all
|
||||
connection: local
|
||||
gather_facts: no
|
||||
tasks:
|
||||
- debug: msg="Container - {{ inventory_hostname }}"
|
||||
|
||||
'''
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
import re
|
||||
import yaml
|
||||
|
||||
from collections import defaultdict
|
||||
# Manipulation of the path is needed because the docker-py
|
||||
# module is imported by the name docker, and because this file
|
||||
# is also named docker
|
||||
for path in [os.getcwd(), '', os.path.dirname(os.path.abspath(__file__))]:
|
||||
try:
|
||||
del sys.path[sys.path.index(path)]
|
||||
except:
|
||||
pass
|
||||
|
||||
HAS_DOCKER_PY = True
|
||||
HAS_DOCKER_ERROR = False
|
||||
|
||||
try:
|
||||
from docker import Client
|
||||
from docker.errors import APIError, TLSParameterError
|
||||
from docker.tls import TLSConfig
|
||||
from docker.constants import DEFAULT_TIMEOUT_SECONDS, DEFAULT_DOCKER_API_VERSION
|
||||
except ImportError as exc:
|
||||
HAS_DOCKER_ERROR = str(exc)
|
||||
HAS_DOCKER_PY = False
|
||||
|
||||
DEFAULT_DOCKER_HOST = 'unix://var/run/docker.sock'
|
||||
DEFAULT_TLS = False
|
||||
DEFAULT_TLS_VERIFY = False
|
||||
DEFAULT_IP = '127.0.0.1'
|
||||
DEFAULT_SSH_PORT = '22'
|
||||
|
||||
BOOLEANS_TRUE = ['yes', 'on', '1', 'true', 1, True]
|
||||
BOOLEANS_FALSE = ['no', 'off', '0', 'false', 0, False]
|
||||
|
||||
|
||||
DOCKER_ENV_ARGS = dict(
|
||||
config_file='DOCKER_CONFIG_FILE',
|
||||
docker_host='DOCKER_HOST',
|
||||
api_version='DOCKER_API_VERSION',
|
||||
cert_path='DOCKER_CERT_PATH',
|
||||
ssl_version='DOCKER_SSL_VERSION',
|
||||
tls='DOCKER_TLS',
|
||||
tls_verify='DOCKER_TLS_VERIFY',
|
||||
timeout='DOCKER_TIMEOUT',
|
||||
private_ssh_port='DOCKER_DEFAULT_SSH_PORT',
|
||||
default_ip='DOCKER_DEFAULT_IP',
|
||||
)
|
||||
|
||||
|
||||
def fail(msg):
|
||||
sys.stderr.write("%s\n" % msg)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def log(msg, pretty_print=False):
|
||||
if pretty_print:
|
||||
print(json.dumps(msg, sort_keys=True, indent=2))
|
||||
else:
|
||||
print(msg + u'\n')
|
||||
|
||||
|
||||
class AnsibleDockerClient(Client):
|
||||
def __init__(self, auth_params, debug):
|
||||
|
||||
self.auth_params = auth_params
|
||||
self.debug = debug
|
||||
self._connect_params = self._get_connect_params()
|
||||
|
||||
try:
|
||||
super(AnsibleDockerClient, self).__init__(**self._connect_params)
|
||||
except APIError as exc:
|
||||
self.fail("Docker API error: %s" % exc)
|
||||
except Exception as exc:
|
||||
self.fail("Error connecting: %s" % exc)
|
||||
|
||||
def fail(self, msg):
|
||||
fail(msg)
|
||||
|
||||
def log(self, msg, pretty_print=False):
|
||||
if self.debug:
|
||||
log(msg, pretty_print)
|
||||
|
||||
def _get_tls_config(self, **kwargs):
|
||||
self.log("get_tls_config:")
|
||||
for key in kwargs:
|
||||
self.log(" %s: %s" % (key, kwargs[key]))
|
||||
try:
|
||||
tls_config = TLSConfig(**kwargs)
|
||||
return tls_config
|
||||
except TLSParameterError as exc:
|
||||
self.fail("TLS config error: %s" % exc)
|
||||
|
||||
def _get_connect_params(self):
|
||||
auth = self.auth_params
|
||||
|
||||
self.log("auth params:")
|
||||
for key in auth:
|
||||
self.log(" %s: %s" % (key, auth[key]))
|
||||
|
||||
if auth['tls'] or auth['tls_verify']:
|
||||
auth['docker_host'] = auth['docker_host'].replace('tcp://', 'https://')
|
||||
|
||||
if auth['tls'] and auth['cert_path'] and auth['key_path']:
|
||||
# TLS with certs and no host verification
|
||||
tls_config = self._get_tls_config(client_cert=(auth['cert_path'], auth['key_path']),
|
||||
verify=False,
|
||||
ssl_version=auth['ssl_version'])
|
||||
return dict(base_url=auth['docker_host'],
|
||||
tls=tls_config,
|
||||
version=auth['api_version'],
|
||||
timeout=auth['timeout'])
|
||||
|
||||
if auth['tls']:
|
||||
# TLS with no certs and not host verification
|
||||
tls_config = self._get_tls_config(verify=False,
|
||||
ssl_version=auth['ssl_version'])
|
||||
return dict(base_url=auth['docker_host'],
|
||||
tls=tls_config,
|
||||
version=auth['api_version'],
|
||||
timeout=auth['timeout'])
|
||||
|
||||
if auth['tls_verify'] and auth['cert_path'] and auth['key_path']:
|
||||
# TLS with certs and host verification
|
||||
if auth['cacert_path']:
|
||||
tls_config = self._get_tls_config(client_cert=(auth['cert_path'], auth['key_path']),
|
||||
ca_cert=auth['cacert_path'],
|
||||
verify=True,
|
||||
assert_hostname=auth['tls_hostname'],
|
||||
ssl_version=auth['ssl_version'])
|
||||
else:
|
||||
tls_config = self._get_tls_config(client_cert=(auth['cert_path'], auth['key_path']),
|
||||
verify=True,
|
||||
assert_hostname=auth['tls_hostname'],
|
||||
ssl_version=auth['ssl_version'])
|
||||
|
||||
return dict(base_url=auth['docker_host'],
|
||||
tls=tls_config,
|
||||
version=auth['api_version'],
|
||||
timeout=auth['timeout'])
|
||||
|
||||
if auth['tls_verify'] and auth['cacert_path']:
|
||||
# TLS with cacert only
|
||||
tls_config = self._get_tls_config(ca_cert=auth['cacert_path'],
|
||||
assert_hostname=auth['tls_hostname'],
|
||||
verify=True,
|
||||
ssl_version=auth['ssl_version'])
|
||||
return dict(base_url=auth['docker_host'],
|
||||
tls=tls_config,
|
||||
version=auth['api_version'],
|
||||
timeout=auth['timeout'])
|
||||
|
||||
if auth['tls_verify']:
|
||||
# TLS with verify and no certs
|
||||
tls_config = self._get_tls_config(verify=True,
|
||||
assert_hostname=auth['tls_hostname'],
|
||||
ssl_version=auth['ssl_version'])
|
||||
return dict(base_url=auth['docker_host'],
|
||||
tls=tls_config,
|
||||
version=auth['api_version'],
|
||||
timeout=auth['timeout'])
|
||||
# No TLS
|
||||
return dict(base_url=auth['docker_host'],
|
||||
version=auth['api_version'],
|
||||
timeout=auth['timeout'])
|
||||
|
||||
def _handle_ssl_error(self, error):
|
||||
match = re.match(r"hostname.*doesn\'t match (\'.*\')", str(error))
|
||||
if match:
|
||||
msg = "You asked for verification that Docker host name matches %s. The actual hostname is %s. " \
|
||||
"Most likely you need to set DOCKER_TLS_HOSTNAME or pass tls_hostname with a value of %s. " \
|
||||
"You may also use TLS without verification by setting the tls parameter to true." \
|
||||
% (self.auth_params['tls_hostname'], match.group(1))
|
||||
self.fail(msg)
|
||||
self.fail("SSL Exception: %s" % (error))
|
||||
|
||||
|
||||
class EnvArgs(object):
|
||||
def __init__(self):
|
||||
self.config_file = None
|
||||
self.docker_host = None
|
||||
self.api_version = None
|
||||
self.cert_path = None
|
||||
self.ssl_version = None
|
||||
self.tls = None
|
||||
self.tls_verify = None
|
||||
self.tls_hostname = None
|
||||
self.timeout = None
|
||||
self.default_ssh_port = None
|
||||
self.default_ip = None
|
||||
|
||||
|
||||
class DockerInventory(object):
|
||||
|
||||
def __init__(self):
|
||||
self._args = self._parse_cli_args()
|
||||
self._env_args = self._parse_env_args()
|
||||
self.groups = defaultdict(list)
|
||||
self.hostvars = defaultdict(dict)
|
||||
|
||||
def run(self):
|
||||
config_from_file = self._parse_config_file()
|
||||
if not config_from_file:
|
||||
config_from_file = dict()
|
||||
docker_hosts = self.get_hosts(config_from_file)
|
||||
|
||||
for host in docker_hosts:
|
||||
client = AnsibleDockerClient(host, self._args.debug)
|
||||
self.get_inventory(client, host)
|
||||
|
||||
if not self._args.host:
|
||||
self.groups['docker_hosts'] = [host.get('docker_host') for host in docker_hosts]
|
||||
self.groups['_meta'] = dict(
|
||||
hostvars=self.hostvars
|
||||
)
|
||||
print(self._json_format_dict(self.groups, pretty_print=self._args.pretty))
|
||||
else:
|
||||
print(self._json_format_dict(self.hostvars.get(self._args.host, dict()), pretty_print=self._args.pretty))
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
def get_inventory(self, client, host):
|
||||
|
||||
ssh_port = host.get('default_ssh_port')
|
||||
default_ip = host.get('default_ip')
|
||||
hostname = host.get('docker_host')
|
||||
|
||||
try:
|
||||
containers = client.containers(all=True)
|
||||
except Exception as exc:
|
||||
self.fail("Error fetching containers for host %s - %s" % (hostname, str(exc)))
|
||||
|
||||
for container in containers:
|
||||
id = container.get('Id')
|
||||
short_id = id[:13]
|
||||
|
||||
try:
|
||||
name = container.get('Names', list()).pop(0).lstrip('/')
|
||||
except IndexError:
|
||||
name = short_id
|
||||
|
||||
if not self._args.host or (self._args.host and self._args.host in [name, id, short_id]):
|
||||
try:
|
||||
inspect = client.inspect_container(id)
|
||||
except Exception as exc:
|
||||
self.fail("Error inspecting container %s - %s" % (name, str(exc)))
|
||||
|
||||
running = inspect.get('State', dict()).get('Running')
|
||||
|
||||
# Add container to groups
|
||||
image_name = inspect.get('Config', dict()).get('Image')
|
||||
if image_name:
|
||||
self.groups["image_%s" % (image_name)].append(name)
|
||||
|
||||
self.groups[id].append(name)
|
||||
self.groups[name].append(name)
|
||||
if short_id not in self.groups.keys():
|
||||
self.groups[short_id].append(name)
|
||||
self.groups[hostname].append(name)
|
||||
|
||||
if running is True:
|
||||
self.groups['running'].append(name)
|
||||
else:
|
||||
self.groups['stopped'].append(name)
|
||||
|
||||
# Figure ous ssh IP and Port
|
||||
try:
|
||||
# Lookup the public facing port Nat'ed to ssh port.
|
||||
port = client.port(container, ssh_port)[0]
|
||||
except (IndexError, AttributeError, TypeError):
|
||||
port = dict()
|
||||
|
||||
try:
|
||||
ip = default_ip if port['HostIp'] == '0.0.0.0' else port['HostIp']
|
||||
except KeyError:
|
||||
ip = ''
|
||||
|
||||
facts = dict(
|
||||
ansible_ssh_host=ip,
|
||||
ansible_ssh_port=port.get('HostPort', int()),
|
||||
docker_name=name,
|
||||
docker_short_id=short_id
|
||||
)
|
||||
|
||||
for key in inspect:
|
||||
fact_key = self._slugify(key)
|
||||
facts[fact_key] = inspect.get(key)
|
||||
|
||||
self.hostvars[name].update(facts)
|
||||
|
||||
def _slugify(self, value):
|
||||
return 'docker_%s' % (re.sub('[^\w-]', '_', value).lower().lstrip('_'))
|
||||
|
||||
def get_hosts(self, config):
|
||||
'''
|
||||
Determine the list of docker hosts we need to talk to.
|
||||
|
||||
:param config: dictionary read from config file. can be empty.
|
||||
:return: list of connection dictionaries
|
||||
'''
|
||||
hosts = list()
|
||||
|
||||
hosts_list = config.get('hosts')
|
||||
defaults = config.get('defaults', dict())
|
||||
self.log('defaults:')
|
||||
self.log(defaults, pretty_print=True)
|
||||
def_host = defaults.get('host')
|
||||
def_tls = defaults.get('tls')
|
||||
def_tls_verify = defaults.get('tls_verify')
|
||||
def_tls_hostname = defaults.get('tls_hostname')
|
||||
def_ssl_version = defaults.get('ssl_version')
|
||||
def_cert_path = defaults.get('cert_path')
|
||||
def_cacert_path = defaults.get('cacert_path')
|
||||
def_key_path = defaults.get('key_path')
|
||||
def_version = defaults.get('version')
|
||||
def_timeout = defaults.get('timeout')
|
||||
def_ip = defaults.get('default_ip')
|
||||
def_ssh_port = defaults.get('private_ssh_port')
|
||||
|
||||
if hosts_list:
|
||||
# use hosts from config file
|
||||
for host in hosts_list:
|
||||
docker_host = host.get('host') or def_host or self._args.docker_host or \
|
||||
self._env_args.docker_host or DEFAULT_DOCKER_HOST
|
||||
api_version = host.get('version') or def_version or self._args.api_version or \
|
||||
self._env_args.api_version or DEFAULT_DOCKER_API_VERSION
|
||||
tls_hostname = host.get('tls_hostname') or def_tls_hostname or self._args.tls_hostname or \
|
||||
self._env_args.tls_hostname
|
||||
tls_verify = host.get('tls_verify') or def_tls_verify or self._args.tls_verify or \
|
||||
self._env_args.tls_verify or DEFAULT_TLS_VERIFY
|
||||
tls = host.get('tls') or def_tls or self._args.tls or self._env_args.tls or DEFAULT_TLS
|
||||
ssl_version = host.get('ssl_version') or def_ssl_version or self._args.ssl_version or \
|
||||
self._env_args.ssl_version
|
||||
|
||||
cert_path = host.get('cert_path') or def_cert_path or self._args.cert_path or \
|
||||
self._env_args.cert_path
|
||||
if cert_path and cert_path == self._env_args.cert_path:
|
||||
cert_path = os.path.join(cert_path, 'cert.pem')
|
||||
|
||||
cacert_path = host.get('cacert_path') or def_cacert_path or self._args.cacert_path or \
|
||||
self._env_args.cert_path
|
||||
if cacert_path and cacert_path == self._env_args.cert_path:
|
||||
cacert_path = os.path.join(cacert_path, 'ca.pem')
|
||||
|
||||
key_path = host.get('key_path') or def_key_path or self._args.key_path or \
|
||||
self._env_args.cert_path
|
||||
if key_path and key_path == self._env_args.cert_path:
|
||||
key_path = os.path.join(key_path, 'key.pem')
|
||||
|
||||
timeout = host.get('timeout') or def_timeout or self._args.timeout or self._env_args.timeout or \
|
||||
DEFAULT_TIMEOUT_SECONDS
|
||||
default_ip = host.get('default_ip') or def_ip or self._args.default_ip_address or \
|
||||
DEFAULT_IP
|
||||
default_ssh_port = host.get('private_ssh_port') or def_ssh_port or self._args.private_ssh_port or \
|
||||
DEFAULT_SSH_PORT
|
||||
host_dict = dict(
|
||||
docker_host=docker_host,
|
||||
api_version=api_version,
|
||||
tls=tls,
|
||||
tls_verify=tls_verify,
|
||||
tls_hostname=tls_hostname,
|
||||
cert_path=cert_path,
|
||||
cacert_path=cacert_path,
|
||||
key_path=key_path,
|
||||
ssl_version=ssl_version,
|
||||
timeout=timeout,
|
||||
default_ip=default_ip,
|
||||
default_ssh_port=default_ssh_port,
|
||||
)
|
||||
hosts.append(host_dict)
|
||||
else:
|
||||
# use default definition
|
||||
docker_host = def_host or self._args.docker_host or self._env_args.docker_host or DEFAULT_DOCKER_HOST
|
||||
api_version = def_version or self._args.api_version or self._env_args.api_version or \
|
||||
DEFAULT_DOCKER_API_VERSION
|
||||
tls_hostname = def_tls_hostname or self._args.tls_hostname or self._env_args.tls_hostname
|
||||
tls_verify = def_tls_verify or self._args.tls_verify or self._env_args.tls_verify or DEFAULT_TLS_VERIFY
|
||||
tls = def_tls or self._args.tls or self._env_args.tls or DEFAULT_TLS
|
||||
ssl_version = def_ssl_version or self._args.ssl_version or self._env_args.ssl_version
|
||||
|
||||
cert_path = def_cert_path or self._args.cert_path or self._env_args.cert_path
|
||||
if cert_path and cert_path == self._env_args.cert_path:
|
||||
cert_path = os.path.join(cert_path, 'cert.pem')
|
||||
|
||||
cacert_path = def_cacert_path or self._args.cacert_path or self._env_args.cert_path
|
||||
if cacert_path and cacert_path == self._env_args.cert_path:
|
||||
cacert_path = os.path.join(cacert_path, 'ca.pem')
|
||||
|
||||
key_path = def_key_path or self._args.key_path or self._env_args.cert_path
|
||||
if key_path and key_path == self._env_args.cert_path:
|
||||
key_path = os.path.join(key_path, 'key.pem')
|
||||
|
||||
timeout = def_timeout or self._args.timeout or self._env_args.timeout or DEFAULT_TIMEOUT_SECONDS
|
||||
default_ip = def_ip or self._args.default_ip_address or DEFAULT_IP
|
||||
default_ssh_port = def_ssh_port or self._args.private_ssh_port or DEFAULT_SSH_PORT
|
||||
host_dict = dict(
|
||||
docker_host=docker_host,
|
||||
api_version=api_version,
|
||||
tls=tls,
|
||||
tls_verify=tls_verify,
|
||||
tls_hostname=tls_hostname,
|
||||
cert_path=cert_path,
|
||||
cacert_path=cacert_path,
|
||||
key_path=key_path,
|
||||
ssl_version=ssl_version,
|
||||
timeout=timeout,
|
||||
default_ip=default_ip,
|
||||
default_ssh_port=default_ssh_port,
|
||||
)
|
||||
hosts.append(host_dict)
|
||||
self.log("hosts: ")
|
||||
self.log(hosts, pretty_print=True)
|
||||
return hosts
|
||||
|
||||
def _parse_config_file(self):
|
||||
config = dict()
|
||||
config_path = None
|
||||
|
||||
if self._args.config_file:
|
||||
config_path = self._args.config_file
|
||||
elif self._env_args.config_file:
|
||||
config_path = self._env_args.config_file
|
||||
|
||||
if config_path:
|
||||
try:
|
||||
config_file = os.path.abspath(config_path)
|
||||
except:
|
||||
config_file = None
|
||||
|
||||
if config_file and os.path.exists(config_file):
|
||||
with open(config_file) as f:
|
||||
try:
|
||||
config = yaml.safe_load(f.read())
|
||||
except Exception as exc:
|
||||
self.fail("Error: parsing %s - %s" % (config_path, str(exc)))
|
||||
return config
|
||||
|
||||
def log(self, msg, pretty_print=False):
|
||||
if self._args.debug:
|
||||
log(msg, pretty_print)
|
||||
|
||||
def fail(self, msg):
|
||||
fail(msg)
|
||||
|
||||
def _parse_env_args(self):
|
||||
args = EnvArgs()
|
||||
for key, value in DOCKER_ENV_ARGS.items():
|
||||
if os.environ.get(value):
|
||||
val = os.environ.get(value)
|
||||
if val in BOOLEANS_TRUE:
|
||||
val = True
|
||||
if val in BOOLEANS_FALSE:
|
||||
val = False
|
||||
setattr(args, key, val)
|
||||
return args
|
||||
|
||||
def _parse_cli_args(self):
|
||||
# Parse command line arguments
|
||||
|
||||
basename = os.path.splitext(os.path.basename(__file__))[0]
|
||||
default_config = basename + '.yml'
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Return Ansible inventory for one or more Docker hosts.')
|
||||
parser.add_argument('--list', action='store_true', default=True,
|
||||
help='List all containers (default: True)')
|
||||
parser.add_argument('--debug', action='store_true', default=False,
|
||||
help='Send debug messages to STDOUT')
|
||||
parser.add_argument('--host', action='store',
|
||||
help='Only get information for a specific container.')
|
||||
parser.add_argument('--pretty', action='store_true', default=False,
|
||||
help='Pretty print JSON output(default: False)')
|
||||
parser.add_argument('--config-file', action='store', default=default_config,
|
||||
help="Name of the config file to use. Default is %s" % (default_config))
|
||||
parser.add_argument('--docker-host', action='store', default=None,
|
||||
help="The base url or Unix sock path to connect to the docker daemon. Defaults to %s"
|
||||
% (DEFAULT_DOCKER_HOST))
|
||||
parser.add_argument('--tls-hostname', action='store', default='localhost',
|
||||
help="Host name to expect in TLS certs. Defaults to 'localhost'")
|
||||
parser.add_argument('--api-version', action='store', default=None,
|
||||
help="Docker daemon API version. Defaults to %s" % (DEFAULT_DOCKER_API_VERSION))
|
||||
parser.add_argument('--timeout', action='store', default=None,
|
||||
help="Docker connection timeout in seconds. Defaults to %s"
|
||||
% (DEFAULT_TIMEOUT_SECONDS))
|
||||
parser.add_argument('--cacert-path', action='store', default=None,
|
||||
help="Path to the TLS certificate authority pem file.")
|
||||
parser.add_argument('--cert-path', action='store', default=None,
|
||||
help="Path to the TLS certificate pem file.")
|
||||
parser.add_argument('--key-path', action='store', default=None,
|
||||
help="Path to the TLS encryption key pem file.")
|
||||
parser.add_argument('--ssl-version', action='store', default=None,
|
||||
help="TLS version number")
|
||||
parser.add_argument('--tls', action='store_true', default=None,
|
||||
help="Use TLS. Defaults to %s" % (DEFAULT_TLS))
|
||||
parser.add_argument('--tls-verify', action='store_true', default=None,
|
||||
help="Verify TLS certificates. Defaults to %s" % (DEFAULT_TLS_VERIFY))
|
||||
parser.add_argument('--private-ssh-port', action='store', default=None,
|
||||
help="Default private container SSH Port. Defaults to %s" % (DEFAULT_SSH_PORT))
|
||||
parser.add_argument('--default-ip-address', action='store', default=None,
|
||||
help="Default container SSH IP address. Defaults to %s" % (DEFAULT_IP))
|
||||
return parser.parse_args()
|
||||
|
||||
def _json_format_dict(self, data, pretty_print=False):
|
||||
# format inventory data for output
|
||||
if pretty_print:
|
||||
return json.dumps(data, sort_keys=True, indent=4)
|
||||
else:
|
||||
return json.dumps(data)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
if not HAS_DOCKER_PY:
|
||||
fail("Failed to import docker-py. Try `pip install docker-py` - %s" % (HAS_DOCKER_ERROR))
|
||||
|
||||
DockerInventory().run()
|
||||
|
||||
main()
|
||||
@ -1,74 +0,0 @@
|
||||
# This is the configuration file for the Docker inventory script: docker_inventory.py.
|
||||
#
|
||||
# You can define the following in this file:
|
||||
#
|
||||
# defaults
|
||||
# Defines a default connection. Defaults will be taken from this and applied to any values not provided
|
||||
# for a host defined in the hosts list.
|
||||
#
|
||||
# hosts
|
||||
# If you wish to get inventory from more than one Docker host, define a hosts list.
|
||||
#
|
||||
# For the default host and each host in the hosts list define the following attributes:
|
||||
#
|
||||
# host:
|
||||
# description: The URL or Unix socket path used to connect to the Docker API.
|
||||
# required: yes
|
||||
#
|
||||
# tls:
|
||||
# description: Connect using TLS without verifying the authenticity of the Docker host server.
|
||||
# default: false
|
||||
# required: false
|
||||
#
|
||||
# tls_verify:
|
||||
# description: Connect using TLS without verifying the authenticity of the Docker host server.
|
||||
# default: false
|
||||
# required: false
|
||||
#
|
||||
# cert_path:
|
||||
# description: Path to the client's TLS certificate file.
|
||||
# default: null
|
||||
# required: false
|
||||
#
|
||||
# cacert_path:
|
||||
# description: Use a CA certificate when performing server verification by providing the path to a CA certificate file.
|
||||
# default: null
|
||||
# required: false
|
||||
#
|
||||
# key_path:
|
||||
# description: Path to the client's TLS key file.
|
||||
# default: null
|
||||
# required: false
|
||||
#
|
||||
# version:
|
||||
# description: The Docker API version.
|
||||
# required: false
|
||||
# default: will be supplied by the docker-py module.
|
||||
#
|
||||
# timeout:
|
||||
# description: The amount of time in seconds to wait on an API response.
|
||||
# required: false
|
||||
# default: 60
|
||||
#
|
||||
# default_ip:
|
||||
# description: The IP address to assign to ansible_host when the container's SSH port is mapped to interface
|
||||
# '0.0.0.0'.
|
||||
# required: false
|
||||
# default: 127.0.0.1
|
||||
#
|
||||
# private_ssh_port:
|
||||
# description: The port containers use for SSH
|
||||
# required: false
|
||||
# default: 22
|
||||
|
||||
#defaults:
|
||||
# host: unix:///var/run/docker.sock
|
||||
# private_ssh_port: 22
|
||||
# default_ip: 127.0.0.1
|
||||
|
||||
#hosts:
|
||||
# - host: tcp://10.45.5.16:4243
|
||||
# private_ssh_port: 2022
|
||||
# default_ip: 172.16.3.45
|
||||
# - host: tcp://localhost:4243
|
||||
# private_ssh_port: 2029
|
||||
@ -1,189 +0,0 @@
|
||||
# Ansible EC2 external inventory script settings
|
||||
#
|
||||
|
||||
[ec2]
|
||||
|
||||
# to talk to a private eucalyptus instance uncomment these lines
|
||||
# and edit edit eucalyptus_host to be the host name of your cloud controller
|
||||
#eucalyptus = True
|
||||
#eucalyptus_host = clc.cloud.domain.org
|
||||
|
||||
# AWS regions to make calls to. Set this to 'all' to make request to all regions
|
||||
# in AWS and merge the results together. Alternatively, set this to a comma
|
||||
# separated list of regions. E.g. 'us-east-1,us-west-1,us-west-2'
|
||||
regions = all
|
||||
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:
|
||||
# http://docs.pythonboto.org/en/latest/ref/ec2.html#module-boto.ec2.instance
|
||||
# Below are 2 variables that are used as the address of a server:
|
||||
# - destination_variable
|
||||
# - vpc_destination_variable
|
||||
|
||||
# This is the normal destination variable to use. If you are running Ansible
|
||||
# from outside EC2, then 'public_dns_name' makes the most sense. If you are
|
||||
# running Ansible from within EC2, then perhaps you want to use the internal
|
||||
# address, and should set this to 'private_dns_name'. The key of an EC2 tag
|
||||
# may optionally be used; however the boto instance variables hold precedence
|
||||
# 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
|
||||
# will not be listed in the inventory until You set:
|
||||
# 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, uncomment and set 'route53' to True.
|
||||
route53 = False
|
||||
|
||||
# To exclude RDS instances from the inventory, uncomment and set to False.
|
||||
#rds = False
|
||||
|
||||
# To exclude ElastiCache instances from the inventory, uncomment and set to False.
|
||||
#elasticache = False
|
||||
|
||||
# Additionally, you can specify the list of zones to exclude looking up in
|
||||
# 'route53_excluded_zones' as a comma-separated list.
|
||||
# route53_excluded_zones = samplezone1.com, samplezone2.com
|
||||
|
||||
# By default, only EC2 instances in the 'running' state are returned. Set
|
||||
# 'all_instances' to True to return all instances regardless of state.
|
||||
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 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.
|
||||
#
|
||||
# Note that all_elasticache_nodes only applies to listed clusters. That means
|
||||
# if you set all_elastic_clusters to false, no node will be return from
|
||||
# unavailable clusters, regardless of the state and to what you set for
|
||||
# all_elasticache_nodes.
|
||||
all_elasticache_replication_groups = False
|
||||
all_elasticache_clusters = False
|
||||
all_elasticache_nodes = False
|
||||
|
||||
# API calls to EC2 are slow. For this reason, we cache the results of an API
|
||||
# call. Set this to the path you want cache files to be written to. Two files
|
||||
# will be written to this directory:
|
||||
# - ansible-ec2.cache
|
||||
# - ansible-ec2.index
|
||||
cache_path = ~/.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
|
||||
|
||||
# If set to true, any tag of the form "a,b,c" is expanded into a list
|
||||
# and the results are used to create additional tag_* inventory groups.
|
||||
expand_csv_tags = False
|
||||
|
||||
# The EC2 inventory output can become very large. To manage its size,
|
||||
# configure which groups should be created.
|
||||
group_by_instance_id = True
|
||||
group_by_region = True
|
||||
group_by_availability_zone = True
|
||||
group_by_ami_id = True
|
||||
group_by_instance_type = True
|
||||
group_by_key_pair = True
|
||||
group_by_vpc_id = True
|
||||
group_by_security_group = True
|
||||
group_by_tag_keys = True
|
||||
group_by_tag_none = True
|
||||
group_by_route53_names = True
|
||||
group_by_rds_engine = True
|
||||
group_by_rds_parameter_group = True
|
||||
group_by_elasticache_engine = True
|
||||
group_by_elasticache_cluster = True
|
||||
group_by_elasticache_parameter_group = True
|
||||
group_by_elasticache_replication_group = 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-*
|
||||
|
||||
# Instance filters can be used to control which instances are retrieved for
|
||||
# inventory. For the full list of possible filters, please read the EC2 API
|
||||
# docs: http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeInstances.html#query-DescribeInstances-filters
|
||||
# Filters are key/value pairs separated by '=', to list multiple filters use
|
||||
# a list separated by commas. See examples below.
|
||||
|
||||
# Retrieve only instances with (key=value) env=staging tag
|
||||
# instance_filters = tag:env=staging
|
||||
|
||||
# Retrieve only instances with role=webservers OR role=dbservers tag
|
||||
# instance_filters = tag:role=webservers,tag:role=dbservers
|
||||
|
||||
# Retrieve only t1.micro instances OR instances with tag env=staging
|
||||
# instance_filters = instance-type=t1.micro,tag:env=staging
|
||||
|
||||
# You can use wildcards in filter values also. Below will list instances which
|
||||
# tag Name value matches webservers1*
|
||||
# (ex. webservers15, webservers1a, webservers123 etc)
|
||||
# instance_filters = tag:Name=webservers1*
|
||||
|
||||
# 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
@ -1,107 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
fleetctl base external inventory script. Automatically finds the IPs of the booted coreos instances and
|
||||
returns it under the host group 'coreos'
|
||||
"""
|
||||
|
||||
# Copyright (C) 2014 Andrew Rothstein <andrew.rothstein at gmail.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#
|
||||
# Thanks to the vagrant.py inventory script for giving me the basic structure
|
||||
# of this.
|
||||
#
|
||||
|
||||
import sys
|
||||
import subprocess
|
||||
import re
|
||||
import string
|
||||
from optparse import OptionParser
|
||||
try:
|
||||
import json
|
||||
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",
|
||||
help="Produce a JSON consumable grouping of servers in your fleet")
|
||||
parser.add_option('--host', default=None, dest="host",
|
||||
help="Generate additional host specific details for given host for Ansible")
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
#
|
||||
# helper functions
|
||||
#
|
||||
|
||||
def get_ssh_config():
|
||||
configs = []
|
||||
for box in list_running_boxes():
|
||||
config = get_a_ssh_config(box)
|
||||
configs.append(config)
|
||||
return configs
|
||||
|
||||
#list all the running instances in the fleet
|
||||
def list_running_boxes():
|
||||
boxes = []
|
||||
for line in subprocess.check_output(["fleetctl", "list-machines"]).split('\n'):
|
||||
matcher = re.search("[^\s]+[\s]+([^\s]+).+", line)
|
||||
if matcher and matcher.group(1) != "IP":
|
||||
boxes.append(matcher.group(1))
|
||||
|
||||
return boxes
|
||||
|
||||
def get_a_ssh_config(box_name):
|
||||
config = {}
|
||||
config['Host'] = box_name
|
||||
config['ansible_ssh_user'] = 'core'
|
||||
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': []}
|
||||
|
||||
for data in ssh_config:
|
||||
hosts['coreos'].append(data['Host'])
|
||||
|
||||
print(json.dumps(hosts))
|
||||
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.
|
||||
result = details[0]
|
||||
result
|
||||
|
||||
print(json.dumps(result))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# Print out help
|
||||
#------------------------------
|
||||
else:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
@ -1,83 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import argparse
|
||||
from ipalib import api
|
||||
import json
|
||||
|
||||
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
|
||||
order for this to work.
|
||||
'''
|
||||
|
||||
api.bootstrap(context='cli')
|
||||
api.finalize()
|
||||
try:
|
||||
api.Backend.rpcclient.connect()
|
||||
except AttributeError:
|
||||
#FreeIPA < 4.0 compatibility
|
||||
api.Backend.xmlclient.connect()
|
||||
|
||||
return api
|
||||
|
||||
def list_groups(api):
|
||||
'''
|
||||
This function returns a list of all host groups. This function requires
|
||||
one argument, the FreeIPA/IPA API object.
|
||||
'''
|
||||
|
||||
inventory = {}
|
||||
hostvars={}
|
||||
meta={}
|
||||
|
||||
result = api.Command.hostgroup_find()['result']
|
||||
|
||||
for hostgroup in result:
|
||||
inventory[hostgroup['cn'][0]] = { 'hosts': [host for host in hostgroup['member_host']]}
|
||||
|
||||
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.
|
||||
This function expects no arguments.
|
||||
'''
|
||||
|
||||
parser = argparse.ArgumentParser(description='Ansible FreeIPA/IPA '
|
||||
'inventory module')
|
||||
group = parser.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument('--list', action='store_true',
|
||||
help='List active servers')
|
||||
group.add_argument('--host', help='List details about the specified host')
|
||||
|
||||
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
|
||||
FreeIPA/IPA.
|
||||
|
||||
This function expects one string, this hostname to lookup variables for.
|
||||
'''
|
||||
|
||||
print(json.dumps({}))
|
||||
|
||||
return None
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = parse_args()
|
||||
|
||||
if args.host:
|
||||
print_host(args.host)
|
||||
elif args.list:
|
||||
api = initialize()
|
||||
list_groups(api)
|
||||
@ -1,128 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# (c) 2015, Marc Abramowitz <marca@surveymonkey.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/>.
|
||||
|
||||
# Dynamic inventory script which lets you use nodes discovered by Canonical's
|
||||
# Landscape (http://www.ubuntu.com/management/landscape-features).
|
||||
#
|
||||
# Requires the `landscape_api` Python module
|
||||
# See:
|
||||
# - https://landscape.canonical.com/static/doc/api/api-client-package.html
|
||||
# - https://landscape.canonical.com/static/doc/api/python-api.html
|
||||
#
|
||||
# Environment variables
|
||||
# ---------------------
|
||||
# - `LANDSCAPE_API_URI`
|
||||
# - `LANDSCAPE_API_KEY`
|
||||
# - `LANDSCAPE_API_SECRET`
|
||||
# - `LANDSCAPE_API_SSL_CA_FILE` (optional)
|
||||
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
import os
|
||||
import sys
|
||||
|
||||
from landscape_api.base import API, HTTPError
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
_key = 'landscape'
|
||||
|
||||
|
||||
class EnvironmentConfig(object):
|
||||
uri = os.getenv('LANDSCAPE_API_URI')
|
||||
access_key = os.getenv('LANDSCAPE_API_KEY')
|
||||
secret_key = os.getenv('LANDSCAPE_API_SECRET')
|
||||
ssl_ca_file = os.getenv('LANDSCAPE_API_SSL_CA_FILE')
|
||||
|
||||
|
||||
def _landscape_client():
|
||||
env = EnvironmentConfig()
|
||||
return API(
|
||||
uri=env.uri,
|
||||
access_key=env.access_key,
|
||||
secret_key=env.secret_key,
|
||||
ssl_ca_file=env.ssl_ca_file)
|
||||
|
||||
|
||||
def get_landscape_members_data():
|
||||
return _landscape_client().get_computers()
|
||||
|
||||
|
||||
def get_nodes(data):
|
||||
return [node['hostname'] for node in data]
|
||||
|
||||
|
||||
def get_groups(data):
|
||||
groups = collections.defaultdict(list)
|
||||
|
||||
for node in data:
|
||||
for value in node['tags']:
|
||||
groups[value].append(node['hostname'])
|
||||
|
||||
return groups
|
||||
|
||||
|
||||
def get_meta(data):
|
||||
meta = {'hostvars': {}}
|
||||
for node in data:
|
||||
meta['hostvars'][node['hostname']] = {'tags': node['tags']}
|
||||
return meta
|
||||
|
||||
|
||||
def print_list():
|
||||
data = get_landscape_members_data()
|
||||
nodes = get_nodes(data)
|
||||
groups = get_groups(data)
|
||||
meta = get_meta(data)
|
||||
inventory_data = {_key: nodes, '_meta': meta}
|
||||
inventory_data.update(groups)
|
||||
print(json.dumps(inventory_data))
|
||||
|
||||
|
||||
def print_host(host):
|
||||
data = get_landscape_members_data()
|
||||
meta = get_meta(data)
|
||||
print(json.dumps(meta['hostvars'][host]))
|
||||
|
||||
|
||||
def get_args(args_list):
|
||||
parser = argparse.ArgumentParser(
|
||||
description='ansible inventory script reading from landscape cluster')
|
||||
mutex_group = parser.add_mutually_exclusive_group(required=True)
|
||||
help_list = 'list all hosts from landscape cluster'
|
||||
mutex_group.add_argument('--list', action='store_true', help=help_list)
|
||||
help_host = 'display variables for a host'
|
||||
mutex_group.add_argument('--host', help=help_host)
|
||||
return parser.parse_args(args_list)
|
||||
|
||||
|
||||
def main(args_list):
|
||||
args = get_args(args_list)
|
||||
if args.list:
|
||||
print_list()
|
||||
if args.host:
|
||||
print_host(args.host)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
||||
@ -1,67 +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)
|
||||
@ -1,10 +0,0 @@
|
||||
# Ansible Nagios external inventory script settings
|
||||
#
|
||||
|
||||
[ndo]
|
||||
# NDO database URI
|
||||
# Make sure that data is returned as strings and not bytes if using python 3.
|
||||
# See http://docs.sqlalchemy.org/en/rel_1_0/core/engines.html
|
||||
# for supported databases and URI format.
|
||||
# Example for mysqlclient module :
|
||||
database_uri=mysql+mysqldb://user:passwd@hostname/ndo?charset=utf8&use_unicode=1
|
||||
@ -1,109 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# (c) 2014, Jonathan Lestrelin <jonathan.lestrelin@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 NDO external inventory script.
|
||||
========================================
|
||||
|
||||
Returns hosts and hostgroups from Nagios NDO.
|
||||
|
||||
Configuration is read from `nagios_ndo.ini`.
|
||||
"""
|
||||
|
||||
import os
|
||||
import argparse
|
||||
try:
|
||||
import configparser
|
||||
except ImportError:
|
||||
import ConfigParser
|
||||
configparser = ConfigParser
|
||||
import json
|
||||
|
||||
try:
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.engine import create_engine
|
||||
except ImportError:
|
||||
print("Error: SQLAlchemy is needed. Try something like: pip install sqlalchemy")
|
||||
exit(1)
|
||||
|
||||
class NagiosNDOInventory(object):
|
||||
|
||||
def read_settings(self):
|
||||
config = configparser.SafeConfigParser()
|
||||
config.read(os.path.dirname(os.path.realpath(__file__)) + '/nagios_ndo.ini')
|
||||
if config.has_option('ndo', 'database_uri'):
|
||||
self.ndo_database_uri = config.get('ndo', 'database_uri')
|
||||
|
||||
def read_cli(self):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--host', nargs=1)
|
||||
parser.add_argument('--list', action='store_true')
|
||||
self.options = parser.parse_args()
|
||||
|
||||
def get_hosts(self):
|
||||
engine = create_engine(self.ndo_database_uri)
|
||||
connection = engine.connect()
|
||||
select_hosts = text("SELECT display_name \
|
||||
FROM nagios_hosts")
|
||||
select_hostgroups = text("SELECT alias \
|
||||
FROM nagios_hostgroups")
|
||||
select_hostgroup_hosts = text("SELECT h.display_name \
|
||||
FROM nagios_hostgroup_members hgm, nagios_hosts h, nagios_hostgroups hg \
|
||||
WHERE hgm.hostgroup_id = hg.hostgroup_id \
|
||||
AND hgm.host_object_id = h.host_object_id \
|
||||
AND hg.alias =:hostgroup_alias")
|
||||
|
||||
hosts = connection.execute(select_hosts)
|
||||
self.result['all']['hosts'] = [host['display_name'] for host in hosts]
|
||||
|
||||
for hostgroup in connection.execute(select_hostgroups):
|
||||
hostgroup_alias = hostgroup['alias']
|
||||
self.result[hostgroup_alias] = {}
|
||||
hosts = connection.execute(select_hostgroup_hosts, hostgroup_alias=hostgroup_alias)
|
||||
self.result[hostgroup_alias]['hosts'] = [host['display_name'] for host in hosts]
|
||||
|
||||
def __init__(self):
|
||||
|
||||
self.defaultgroup = 'group_all'
|
||||
self.ndo_database_uri = None
|
||||
self.options = None
|
||||
|
||||
self.read_settings()
|
||||
self.read_cli()
|
||||
|
||||
self.result = {}
|
||||
self.result['all'] = {}
|
||||
self.result['all']['hosts'] = []
|
||||
self.result['_meta'] = {}
|
||||
self.result['_meta']['hostvars'] = {}
|
||||
|
||||
if self.ndo_database_uri:
|
||||
self.get_hosts()
|
||||
if self.options.host:
|
||||
print(json.dumps({}))
|
||||
elif self.options.list:
|
||||
print(json.dumps(self.result))
|
||||
else:
|
||||
print("usage: --list or --host HOSTNAME")
|
||||
exit(1)
|
||||
else:
|
||||
print("Error: Database configuration is missing. See nagios_ndo.ini.")
|
||||
exit(1)
|
||||
|
||||
NagiosNDOInventory()
|
||||
@ -1,48 +0,0 @@
|
||||
# Ansible OpenStack external inventory script
|
||||
# DEPRECATED: please use openstack.py inventory which is configured for
|
||||
# auth using the os-client-config library and either clouds.yaml or standard
|
||||
# openstack environment variables
|
||||
|
||||
[openstack]
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Required settings
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
# API version
|
||||
version = 2
|
||||
|
||||
# OpenStack nova username
|
||||
username =
|
||||
|
||||
# OpenStack nova api_key or password
|
||||
api_key =
|
||||
|
||||
# OpenStack nova auth_url
|
||||
auth_url =
|
||||
|
||||
# OpenStack nova project_id or tenant name
|
||||
project_id =
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Optional settings
|
||||
#-------------------------------------------------------------------------
|
||||
|
||||
# Authentication system
|
||||
# auth_system = keystone
|
||||
|
||||
# Serverarm region name to use
|
||||
# region_name =
|
||||
|
||||
# Specify a preference for public or private IPs (public is default)
|
||||
# prefer_private = False
|
||||
|
||||
# What service type (required for newer nova client)
|
||||
# service_type = compute
|
||||
|
||||
|
||||
# TODO: Some other options
|
||||
# insecure =
|
||||
# endpoint_type =
|
||||
# extensions =
|
||||
# service_name =
|
||||
@ -1,229 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# (c) 2012, Marco Vito Moscaritolo <marco@agavee.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/>.
|
||||
|
||||
# WARNING: This file is deprecated. New work should focus on the openstack.py
|
||||
# inventory module, which properly handles multiple clouds as well as keystone
|
||||
# v3 and keystone auth plugins
|
||||
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
import ConfigParser
|
||||
from novaclient import client as nova_client
|
||||
from six import iteritems, itervalues
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
|
||||
sys.stderr.write("WARNING: this inventory module is deprecated. please migrate usage to openstack.py\n")
|
||||
|
||||
###################################################
|
||||
# executed with no parameters, return the list of
|
||||
# all groups and hosts
|
||||
|
||||
NOVA_CONFIG_FILES = [os.getcwd() + "/nova.ini",
|
||||
os.path.expanduser(os.environ.get('ANSIBLE_CONFIG', "~/nova.ini")),
|
||||
"/etc/ansible/nova.ini"]
|
||||
|
||||
NOVA_DEFAULTS = {
|
||||
'auth_system': None,
|
||||
'region_name': None,
|
||||
'service_type': 'compute',
|
||||
}
|
||||
|
||||
|
||||
def nova_load_config_file():
|
||||
p = ConfigParser.SafeConfigParser(NOVA_DEFAULTS)
|
||||
|
||||
for path in NOVA_CONFIG_FILES:
|
||||
if os.path.exists(path):
|
||||
p.read(path)
|
||||
return p
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_fallback(config, value, section="openstack"):
|
||||
"""
|
||||
Get value from config object and return the value
|
||||
or false
|
||||
"""
|
||||
try:
|
||||
return config.get(section, value)
|
||||
except ConfigParser.NoOptionError:
|
||||
return False
|
||||
|
||||
|
||||
def push(data, key, element):
|
||||
"""
|
||||
Assist in items to a dictionary of lists
|
||||
"""
|
||||
if (not element) or (not key):
|
||||
return
|
||||
|
||||
if key in data:
|
||||
data[key].append(element)
|
||||
else:
|
||||
data[key] = [element]
|
||||
|
||||
|
||||
def to_safe(word):
|
||||
'''
|
||||
Converts 'bad' characters in a string to underscores so they can
|
||||
be used as Ansible groups
|
||||
'''
|
||||
return re.sub(r"[^A-Za-z0-9\-]", "_", word)
|
||||
|
||||
|
||||
def get_ips(server, access_ip=True):
|
||||
"""
|
||||
Returns a list of the server's IPs, or the preferred
|
||||
access IP
|
||||
"""
|
||||
private = []
|
||||
public = []
|
||||
address_list = []
|
||||
# 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 address in network:
|
||||
if address.get('OS-EXT-IPS:type', False) == 'fixed':
|
||||
private.append(address['addr'])
|
||||
elif address.get('OS-EXT-IPS:type', False) == 'floating':
|
||||
public.append(address['addr'])
|
||||
|
||||
if not access_ip:
|
||||
address_list.append(server.accessIPv4)
|
||||
address_list.extend(private)
|
||||
address_list.extend(public)
|
||||
return address_list
|
||||
|
||||
access_ip = None
|
||||
# Append group to list
|
||||
if server.accessIPv4:
|
||||
access_ip = server.accessIPv4
|
||||
if (not access_ip) and public and not (private and prefer_private):
|
||||
access_ip = public[0]
|
||||
if private and not access_ip:
|
||||
access_ip = private[0]
|
||||
|
||||
return access_ip
|
||||
|
||||
|
||||
def get_metadata(server):
|
||||
"""Returns dictionary of all host metadata"""
|
||||
get_ips(server, False)
|
||||
results = {}
|
||||
for key in vars(server):
|
||||
# Extract value
|
||||
value = getattr(server, key)
|
||||
|
||||
# Generate sanitized key
|
||||
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
|
||||
if key != 'os_manager':
|
||||
results[key] = value
|
||||
return results
|
||||
|
||||
config = nova_load_config_file()
|
||||
if not config:
|
||||
sys.exit('Unable to find configfile in %s' % ', '.join(NOVA_CONFIG_FILES))
|
||||
|
||||
# Load up connections info based on config and then environment
|
||||
# variables
|
||||
username = (get_fallback(config, 'username') or
|
||||
os.environ.get('OS_USERNAME', None))
|
||||
api_key = (get_fallback(config, 'api_key') or
|
||||
os.environ.get('OS_PASSWORD', None))
|
||||
auth_url = (get_fallback(config, 'auth_url') or
|
||||
os.environ.get('OS_AUTH_URL', None))
|
||||
project_id = (get_fallback(config, 'project_id') or
|
||||
os.environ.get('OS_TENANT_NAME', None))
|
||||
region_name = (get_fallback(config, 'region_name') or
|
||||
os.environ.get('OS_REGION_NAME', None))
|
||||
auth_system = (get_fallback(config, 'auth_system') or
|
||||
os.environ.get('OS_AUTH_SYSTEM', None))
|
||||
|
||||
# Determine what type of IP is preferred to return
|
||||
prefer_private = False
|
||||
try:
|
||||
prefer_private = config.getboolean('openstack', 'prefer_private')
|
||||
except ConfigParser.NoOptionError:
|
||||
pass
|
||||
|
||||
client = nova_client.Client(
|
||||
version=config.get('openstack', 'version'),
|
||||
username=username,
|
||||
api_key=api_key,
|
||||
auth_url=auth_url,
|
||||
region_name=region_name,
|
||||
project_id=project_id,
|
||||
auth_system=auth_system,
|
||||
service_type=config.get('openstack', 'service_type'),
|
||||
)
|
||||
|
||||
# Default or added list option
|
||||
if (len(sys.argv) == 2 and sys.argv[1] == '--list') or len(sys.argv) == 1:
|
||||
groups = {'_meta': {'hostvars': {}}}
|
||||
# Cycle on servers
|
||||
for server in client.servers.list():
|
||||
access_ip = get_ips(server)
|
||||
|
||||
# Push to name group of 1
|
||||
push(groups, server.name, access_ip)
|
||||
|
||||
# Run through each metadata item and add instance to it
|
||||
for key, value in iteritems(server.metadata):
|
||||
composed_key = to_safe('tag_{0}_{1}'.format(key, value))
|
||||
push(groups, composed_key, access_ip)
|
||||
|
||||
# Do special handling of group for backwards compat
|
||||
# inventory groups
|
||||
group = server.metadata['group'] if 'group' in server.metadata else 'undefined'
|
||||
push(groups, group, access_ip)
|
||||
|
||||
# Add vars to _meta key for performance optimization in
|
||||
# Ansible 1.3+
|
||||
groups['_meta']['hostvars'][access_ip] = get_metadata(server)
|
||||
|
||||
# Return server list
|
||||
print(json.dumps(groups, sort_keys=True, indent=2))
|
||||
sys.exit(0)
|
||||
|
||||
#####################################################
|
||||
# executed with a hostname as a parameter, return the
|
||||
# variables for that host
|
||||
|
||||
elif len(sys.argv) == 3 and (sys.argv[1] == '--host'):
|
||||
results = {}
|
||||
ips = []
|
||||
for server in client.servers.list():
|
||||
if sys.argv[2] in (get_ips(server) or []):
|
||||
results = get_metadata(server)
|
||||
print(json.dumps(results, sort_keys=True, indent=2))
|
||||
sys.exit(0)
|
||||
|
||||
else:
|
||||
sys.exit("usage: --list ..OR.. --host <hostname>")
|
||||
@ -1,342 +0,0 @@
|
||||
#!/bin/env python
|
||||
|
||||
'''
|
||||
nsot
|
||||
====
|
||||
|
||||
Ansible Dynamic Inventory to pull hosts from NSoT, a flexible CMDB by Dropbox
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* Define host groups in form of NSoT device attribute criteria
|
||||
|
||||
* All parameters defined by the spec as of 2015-09-05 are supported.
|
||||
|
||||
+ ``--list``: Returns JSON hash of host groups -> hosts and top-level
|
||||
``_meta`` -> ``hostvars`` which correspond to all device attributes.
|
||||
|
||||
Group vars can be specified in the YAML configuration, noted below.
|
||||
|
||||
+ ``--host <hostname>``: Returns JSON hash where every item is a device
|
||||
attribute.
|
||||
|
||||
* In addition to all attributes assigned to resource being returned, script
|
||||
will also append ``site_id`` and ``id`` as facts to utilize.
|
||||
|
||||
|
||||
Confguration
|
||||
------------
|
||||
|
||||
Since it'd be annoying and failure prone to guess where you're configuration
|
||||
file is, use ``NSOT_INVENTORY_CONFIG`` to specify the path to it.
|
||||
|
||||
This file should adhere to the YAML spec. All top-level variable must be
|
||||
desired Ansible group-name hashed with single 'query' item to define the NSoT
|
||||
attribute query.
|
||||
|
||||
Queries follow the normal NSoT query syntax, `shown here`_
|
||||
|
||||
.. _shown here: https://github.com/dropbox/pynsot#set-queries
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
routers:
|
||||
query: 'deviceType=ROUTER'
|
||||
vars:
|
||||
a: b
|
||||
c: d
|
||||
|
||||
juniper_fw:
|
||||
query: 'deviceType=FIREWALL manufacturer=JUNIPER'
|
||||
|
||||
not_f10:
|
||||
query: '-manufacturer=FORCE10'
|
||||
|
||||
The inventory will automatically use your ``.pynsotrc`` like normal pynsot from
|
||||
cli would, so make sure that's configured appropriately.
|
||||
|
||||
.. note::
|
||||
|
||||
Attributes I'm showing above are influenced from ones that the Trigger
|
||||
project likes. As is the spirit of NSoT, use whichever attributes work best
|
||||
for your workflow.
|
||||
|
||||
If config file is blank or absent, the following default groups will be
|
||||
created:
|
||||
|
||||
* ``routers``: deviceType=ROUTER
|
||||
* ``switches``: deviceType=SWITCH
|
||||
* ``firewalls``: deviceType=FIREWALL
|
||||
|
||||
These are likely not useful for everyone so please use the configuration. :)
|
||||
|
||||
.. note::
|
||||
|
||||
By default, resources will only be returned for what your default
|
||||
site is set for in your ``~/.pynsotrc``.
|
||||
|
||||
If you want to specify, add an extra key under the group for ``site: n``.
|
||||
|
||||
Output Examples
|
||||
---------------
|
||||
|
||||
Here are some examples shown from just calling the command directly::
|
||||
|
||||
$ NSOT_INVENTORY_CONFIG=$PWD/test.yaml ansible_nsot --list | jq '.'
|
||||
{
|
||||
"routers": {
|
||||
"hosts": [
|
||||
"test1.example.com"
|
||||
],
|
||||
"vars": {
|
||||
"cool_level": "very",
|
||||
"group": "routers"
|
||||
}
|
||||
},
|
||||
"firewalls": {
|
||||
"hosts": [
|
||||
"test2.example.com"
|
||||
],
|
||||
"vars": {
|
||||
"cool_level": "enough",
|
||||
"group": "firewalls"
|
||||
}
|
||||
},
|
||||
"_meta": {
|
||||
"hostvars": {
|
||||
"test2.example.com": {
|
||||
"make": "SRX",
|
||||
"site_id": 1,
|
||||
"id": 108
|
||||
},
|
||||
"test1.example.com": {
|
||||
"make": "MX80",
|
||||
"site_id": 1,
|
||||
"id": 107
|
||||
}
|
||||
}
|
||||
},
|
||||
"rtr_and_fw": {
|
||||
"hosts": [
|
||||
"test1.example.com",
|
||||
"test2.example.com"
|
||||
],
|
||||
"vars": {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$ NSOT_INVENTORY_CONFIG=$PWD/test.yaml ansible_nsot --host test1 | jq '.'
|
||||
{
|
||||
"make": "MX80",
|
||||
"site_id": 1,
|
||||
"id": 107
|
||||
}
|
||||
|
||||
'''
|
||||
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
import os
|
||||
import pkg_resources
|
||||
import argparse
|
||||
import json
|
||||
import yaml
|
||||
from textwrap import dedent
|
||||
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)
|
||||
|
||||
|
||||
class NSoTInventory(object):
|
||||
'''NSoT Client object for gather inventory'''
|
||||
|
||||
def __init__(self):
|
||||
self.config = dict()
|
||||
config_env = os.environ.get('NSOT_INVENTORY_CONFIG')
|
||||
if config_env:
|
||||
try:
|
||||
config_file = os.path.abspath(config_env)
|
||||
except IOError: # If file non-existent, use default config
|
||||
self._config_default()
|
||||
except Exception as e:
|
||||
sys.exit('%s\n' % e)
|
||||
|
||||
with open(config_file) as f:
|
||||
try:
|
||||
self.config.update(yaml.safe_load(f))
|
||||
except TypeError: # If empty file, use default config
|
||||
warning('Empty config file')
|
||||
self._config_default()
|
||||
except Exception as e:
|
||||
sys.exit('%s\n' % e)
|
||||
else: # Use defaults if env var missing
|
||||
self._config_default()
|
||||
self.groups = self.config.keys()
|
||||
self.client = get_api_client()
|
||||
self._meta = {'hostvars': dict()}
|
||||
|
||||
def _config_default(self):
|
||||
default_yaml = '''
|
||||
---
|
||||
routers:
|
||||
query: deviceType=ROUTER
|
||||
switches:
|
||||
query: deviceType=SWITCH
|
||||
firewalls:
|
||||
query: deviceType=FIREWALL
|
||||
'''
|
||||
self.config = yaml.safe_load(dedent(default_yaml))
|
||||
|
||||
def do_list(self):
|
||||
'''Direct callback for when ``--list`` is provided
|
||||
|
||||
Relies on the configuration generated from init to run
|
||||
_inventory_group()
|
||||
'''
|
||||
inventory = dict()
|
||||
for group, contents in self.config.items():
|
||||
group_response = self._inventory_group(group, contents)
|
||||
inventory.update(group_response)
|
||||
inventory.update({'_meta': self._meta})
|
||||
return json.dumps(inventory)
|
||||
|
||||
def do_host(self, host):
|
||||
return json.dumps(self._hostvars(host))
|
||||
|
||||
def _hostvars(self, host):
|
||||
'''Return dictionary of all device attributes
|
||||
|
||||
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()['data']['devices']
|
||||
if host in i['hostname']][0]
|
||||
attributes = device['attributes']
|
||||
attributes.update({'site_id': device['site_id'], 'id': device['id']})
|
||||
return attributes
|
||||
|
||||
def _inventory_group(self, group, contents):
|
||||
'''Takes a group and returns inventory for it as dict
|
||||
|
||||
:param group: Group name
|
||||
:type group: str
|
||||
:param contents: The contents of the group's YAML config
|
||||
:type contents: dict
|
||||
|
||||
contents param should look like::
|
||||
|
||||
{
|
||||
'query': 'xx',
|
||||
'vars':
|
||||
'a': 'b'
|
||||
}
|
||||
|
||||
Will return something like::
|
||||
|
||||
{ group: {
|
||||
hosts: [],
|
||||
vars: {},
|
||||
}
|
||||
'''
|
||||
query = contents.get('query')
|
||||
hostvars = contents.get('vars', dict())
|
||||
site = contents.get('site', dict())
|
||||
obj = {group: dict()}
|
||||
obj[group]['hosts'] = []
|
||||
obj[group]['vars'] = hostvars
|
||||
try:
|
||||
assert isinstance(query, string_types)
|
||||
except:
|
||||
sys.exit('ERR: Group queries must be a single string\n'
|
||||
' Group: %s\n'
|
||||
' Query: %s\n' % (group, query)
|
||||
)
|
||||
try:
|
||||
if site:
|
||||
site = self.client.sites(site)
|
||||
devices = site.devices.query.get(query=query)
|
||||
else:
|
||||
devices = self.client.devices.query.get(query=query)
|
||||
except HttpServerError as e:
|
||||
if '500' in str(e.response):
|
||||
_site = 'Correct site id?'
|
||||
_attr = 'Queried attributes actually exist?'
|
||||
questions = _site + '\n' + _attr
|
||||
sys.exit('ERR: 500 from server.\n%s' % questions)
|
||||
else:
|
||||
raise
|
||||
except UsageError:
|
||||
sys.exit('ERR: Could not connect to server. Running?')
|
||||
|
||||
# Would do a list comprehension here, but would like to save code/time
|
||||
# and also acquire attributes in this step
|
||||
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
|
||||
# return
|
||||
hostname = host['hostname']
|
||||
obj[group]['hosts'].append(hostname)
|
||||
attributes = host['attributes']
|
||||
attributes.update({'site_id': host['site_id'], 'id': host['id']})
|
||||
self._meta['hostvars'].update({hostname: attributes})
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
def parse_args():
|
||||
desc = __doc__.splitlines()[4] # Just to avoid being redundant
|
||||
|
||||
# Establish parser with options and error out if no action provided
|
||||
parser = argparse.ArgumentParser(
|
||||
description=desc,
|
||||
conflict_handler='resolve',
|
||||
)
|
||||
|
||||
# Arguments
|
||||
#
|
||||
# Currently accepting (--list | -l) and (--host | -h)
|
||||
# These must not be allowed together
|
||||
parser.add_argument(
|
||||
'--list', '-l',
|
||||
help='Print JSON object containing hosts to STDOUT',
|
||||
action='store_true',
|
||||
dest='list_', # Avoiding syntax highlighting for list
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--host', '-h',
|
||||
help='Print JSON object containing hostvars for <host>',
|
||||
action='store',
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.list_ and not args.host: # Require at least one option
|
||||
parser.exit(status=1, message='No action requested')
|
||||
|
||||
if args.list_ and args.host: # Do not allow multiple options
|
||||
parser.exit(status=1, message='Too many actions requested')
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def main():
|
||||
'''Set up argument handling and callback routing'''
|
||||
args = parse_args()
|
||||
client = NSoTInventory()
|
||||
|
||||
# Callback condition
|
||||
if args.list_:
|
||||
print(client.do_list())
|
||||
elif args.host:
|
||||
print(client.do_host(args.host))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -1,22 +0,0 @@
|
||||
---
|
||||
juniper_routers:
|
||||
query: 'deviceType=ROUTER manufacturer=JUNIPER'
|
||||
vars:
|
||||
group: juniper_routers
|
||||
netconf: true
|
||||
os: junos
|
||||
|
||||
cisco_asa:
|
||||
query: 'manufacturer=CISCO deviceType=FIREWALL'
|
||||
vars:
|
||||
group: cisco_asa
|
||||
routed_vpn: false
|
||||
stateful: true
|
||||
|
||||
old_cisco_asa:
|
||||
query: 'manufacturer=CISCO deviceType=FIREWALL -softwareVersion=8.3+'
|
||||
vars:
|
||||
old_nat: true
|
||||
|
||||
not_f10:
|
||||
query: '-manufacturer=FORCE10'
|
||||
@ -1,246 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# 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
|
||||
# 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/>.
|
||||
|
||||
# The OpenStack Inventory module uses os-client-config for configuration.
|
||||
# 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.
|
||||
# If you want to configure via clouds.yaml, you can put the file in:
|
||||
# - Current directory
|
||||
# - ~/.config/openstack/clouds.yaml
|
||||
# - /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 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
|
||||
# the inventory section.
|
||||
# expand_hostvars controls whether or not the inventory will make extra API
|
||||
# calls to fill out additional information about each server
|
||||
# use_hostnames changes the behavior from registering every host with its UUID
|
||||
# and making a group of its hostname to only doing this if the
|
||||
# hostname in question has more than one server
|
||||
# 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)
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from distutils.version import StrictVersion
|
||||
|
||||
try:
|
||||
import json
|
||||
except:
|
||||
import simplejson as json
|
||||
|
||||
import os_client_config
|
||||
import shade
|
||||
import shade.inventory
|
||||
|
||||
CONFIG_FILES = ['/etc/ansible/openstack.yaml', '/etc/ansible/openstack.yml']
|
||||
|
||||
|
||||
def get_groups_from_server(server_vars, namegroup=True):
|
||||
groups = []
|
||||
|
||||
region = server_vars['region']
|
||||
cloud = server_vars['cloud']
|
||||
metadata = server_vars.get('metadata', {})
|
||||
|
||||
# Create a group for the cloud
|
||||
groups.append(cloud)
|
||||
|
||||
# Create a group on region
|
||||
groups.append(region)
|
||||
|
||||
# And one by cloud_region
|
||||
groups.append("%s_%s" % (cloud, region))
|
||||
|
||||
# Check if group metadata key in servers' metadata
|
||||
if 'group' in metadata:
|
||||
groups.append(metadata['group'])
|
||||
|
||||
for extra_group in metadata.get('groups', '').split(','):
|
||||
if extra_group:
|
||||
groups.append(extra_group.strip())
|
||||
|
||||
groups.append('instance-%s' % server_vars['id'])
|
||||
if namegroup:
|
||||
groups.append(server_vars['name'])
|
||||
|
||||
for key in ('flavor', 'image'):
|
||||
if 'name' in server_vars[key]:
|
||||
groups.append('%s-%s' % (key, server_vars[key]['name']))
|
||||
|
||||
for key, value in iter(metadata.items()):
|
||||
groups.append('meta-%s_%s' % (key, value))
|
||||
|
||||
az = server_vars.get('az', None)
|
||||
if az:
|
||||
# Make groups for az, region_az and cloud_region_az
|
||||
groups.append(az)
|
||||
groups.append('%s_%s' % (region, az))
|
||||
groups.append('%s_%s_%s' % (cloud, region, az))
|
||||
return groups
|
||||
|
||||
|
||||
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)
|
||||
else:
|
||||
groups = open(cache_file, 'r').read()
|
||||
return groups
|
||||
|
||||
|
||||
def append_hostvars(hostvars, groups, key, server, namegroup=False):
|
||||
hostvars[key] = dict(
|
||||
ansible_ssh_host=server['interface_ip'],
|
||||
openstack=server)
|
||||
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)
|
||||
hostvars = {}
|
||||
list_args = {}
|
||||
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
|
||||
|
||||
for server in inventory.list_hosts(**list_args):
|
||||
|
||||
if 'interface_ip' not in server:
|
||||
continue
|
||||
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])
|
||||
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)
|
||||
groups['_meta'] = {'hostvars': hostvars}
|
||||
return groups
|
||||
|
||||
|
||||
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:
|
||||
mod_time = os.path.getmtime(cache_file)
|
||||
current_time = time.time()
|
||||
if (mod_time + cache_expiration_time) > current_time:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
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 not os.path.exists(cache_path):
|
||||
os.makedirs(cache_path)
|
||||
cache_file = os.path.join(cache_path, 'ansible-inventory.cache')
|
||||
return (cache_file, cache_expiration_time)
|
||||
|
||||
|
||||
def to_json(in_dict):
|
||||
return json.dumps(in_dict, sort_keys=True, indent=2)
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description='OpenStack Inventory Module')
|
||||
parser.add_argument('--private',
|
||||
action='store_true',
|
||||
help='Use private address for ansible host')
|
||||
parser.add_argument('--refresh', action='store_true',
|
||||
help='Refresh cached information')
|
||||
parser.add_argument('--debug', action='store_true', default=False,
|
||||
help='Enable debug output')
|
||||
group = parser.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument('--list', action='store_true',
|
||||
help='List active servers')
|
||||
group.add_argument('--host', help='List details about the specific host')
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
try:
|
||||
config_files = os_client_config.config.CONFIG_FILES + CONFIG_FILES
|
||||
shade.simple_logging(debug=args.debug)
|
||||
inventory_args = dict(
|
||||
refresh=args.refresh,
|
||||
config_files=config_files,
|
||||
private=args.private,
|
||||
)
|
||||
if hasattr(shade.inventory.OpenStackInventory, 'extra_config'):
|
||||
inventory_args.update(dict(
|
||||
config_key='ansible',
|
||||
config_defaults={
|
||||
'use_hostnames': False,
|
||||
'expand_hostvars': True,
|
||||
'fail_on_errors': True,
|
||||
}
|
||||
))
|
||||
|
||||
inventory = shade.inventory.OpenStackInventory(**inventory_args)
|
||||
|
||||
if args.list:
|
||||
output = get_host_groups(inventory, refresh=args.refresh)
|
||||
elif args.host:
|
||||
output = to_json(inventory.get_host(args.host))
|
||||
print(output)
|
||||
except shade.OpenStackCloudException as e:
|
||||
sys.stderr.write('%s\n' % e.message)
|
||||
sys.exit(1)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -1,32 +0,0 @@
|
||||
clouds:
|
||||
mordred:
|
||||
cloud: hp
|
||||
auth:
|
||||
username: mordred@example.com
|
||||
password: my-wonderful-password
|
||||
project_name: mordred-tenant
|
||||
region_name: region-b.geo-1
|
||||
monty:
|
||||
cloud: hp
|
||||
auth:
|
||||
username: monty.taylor@example.com
|
||||
password: another-wonderful-password
|
||||
project_name: monty.taylor@example.com-default-tenant
|
||||
region_name: region-b.geo-1
|
||||
rax:
|
||||
cloud: rackspace
|
||||
auth:
|
||||
username: example
|
||||
password: spectacular-password
|
||||
project_id: 2352426
|
||||
region_name: DFW,ORD,IAD
|
||||
devstack:
|
||||
auth:
|
||||
auth_url: http://127.0.0.1:35357/v2.0/
|
||||
username: stack
|
||||
password: stack
|
||||
project_name: stack
|
||||
ansible:
|
||||
use_hostnames: True
|
||||
expand_hostvars: False
|
||||
fail_on_errors: True
|
||||
@ -1,77 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# openvz.py
|
||||
#
|
||||
# Copyright 2014 jordonr <jordon@beamsyn.net>
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
# Inspired by libvirt_lxc.py inventory script
|
||||
# https://github.com/ansible/ansible/blob/e5ef0eca03cbb6c8950c06dc50d0ca22aa8902f4/plugins/inventory/libvirt_lxc.py
|
||||
#
|
||||
# Groups are determined by the description field of openvz guests
|
||||
# multiple groups can be separated by commas: webserver,dbserver
|
||||
|
||||
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
|
||||
inventory = {'vzhosts': {'hosts': vzhosts}, '_meta': {'hostvars': {}}}
|
||||
#default group, when description not defined
|
||||
default_group = ['vzguest']
|
||||
|
||||
def get_guests():
|
||||
#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)
|
||||
|
||||
#Load Json info of guests
|
||||
json_data = json.loads(pipe.stdout.read())
|
||||
|
||||
#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']}
|
||||
|
||||
#determine group from guest description
|
||||
if j['description'] is not None:
|
||||
groups = j['description'].split(",")
|
||||
else:
|
||||
groups = default_group
|
||||
|
||||
#add guest to inventory
|
||||
for g in groups:
|
||||
if g not in inventory:
|
||||
inventory[g] = {'hosts': []}
|
||||
|
||||
inventory[g]['hosts'].append(j['hostname'])
|
||||
|
||||
return inventory
|
||||
|
||||
|
||||
if len(sys.argv) == 2 and sys.argv[1] == '--list':
|
||||
inv_json = get_guests()
|
||||
print(json.dumps(inv_json, sort_keys=True))
|
||||
elif len(sys.argv) == 3 and sys.argv[1] == '--host':
|
||||
print(json.dumps({}))
|
||||
else:
|
||||
print("Need an argument, either --list or --host <host>")
|
||||
@ -1,33 +0,0 @@
|
||||
# Copyright 2013 Google 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/>.
|
||||
|
||||
|
||||
# Author: Josha Inglis <jinglis@iix.net> based on the gce.ini by Eric Johnson <erjohnso@google.com>
|
||||
|
||||
[ovirt]
|
||||
# ovirt Service Account configuration information can be stored in the
|
||||
# libcloud 'secrets.py' file. Ideally, the 'secrets.py' file will already
|
||||
# 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.
|
||||
ovirt_api_secrets =
|
||||
|
||||
# If you are not going to use a 'secrets.py' file, you can set the necessary
|
||||
# authorization parameters here.
|
||||
ovirt_url =
|
||||
ovirt_username =
|
||||
ovirt_password =
|
||||
@ -1,292 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright 2015 IIX 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 external inventory script
|
||||
=================================
|
||||
|
||||
Generates inventory that Ansible can understand by making API requests to
|
||||
oVirt via the ovirt-engine-sdk-python library.
|
||||
|
||||
When run against a specific host, this script returns the following variables
|
||||
based on the data obtained from the ovirt_sdk Node object:
|
||||
- ovirt_uuid
|
||||
- ovirt_id
|
||||
- ovirt_image
|
||||
- ovirt_machine_type
|
||||
- ovirt_ips
|
||||
- ovirt_name
|
||||
- ovirt_description
|
||||
- ovirt_status
|
||||
- ovirt_zone
|
||||
- ovirt_tags
|
||||
- ovirt_stats
|
||||
|
||||
When run in --list mode, instances are grouped by the following categories:
|
||||
|
||||
- zone:
|
||||
zone group name.
|
||||
- instance tags:
|
||||
An entry is created for each tag. For example, if you have two instances
|
||||
with a common tag called 'foo', they will both be grouped together under
|
||||
the 'tag_foo' name.
|
||||
- network name:
|
||||
the name of the network is appended to 'network_' (e.g. the 'default'
|
||||
network will result in a group named 'network_default')
|
||||
- running status:
|
||||
group name prefixed with 'status_' (e.g. status_up, status_down,..)
|
||||
|
||||
Examples:
|
||||
Execute uname on all instances in the us-central1-a zone
|
||||
$ ansible -i ovirt.py us-central1-a -m shell -a "/bin/uname -a"
|
||||
|
||||
Use the ovirt inventory script to print out instance specific information
|
||||
$ contrib/inventory/ovirt.py --host my_instance
|
||||
|
||||
Author: Josha Inglis <jinglis@iix.net> based on the gce.py by Eric Johnson <erjohnso@google.com>
|
||||
Version: 0.0.1
|
||||
"""
|
||||
|
||||
USER_AGENT_PRODUCT = "Ansible-ovirt_inventory_plugin"
|
||||
USER_AGENT_VERSION = "v1"
|
||||
|
||||
import sys
|
||||
import os
|
||||
import argparse
|
||||
import ConfigParser
|
||||
from collections import defaultdict
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
# noinspection PyUnresolvedReferences,PyPackageRequirements
|
||||
import simplejson as json
|
||||
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
from ovirtsdk.api import API
|
||||
# noinspection PyUnresolvedReferences
|
||||
from ovirtsdk.xml import params
|
||||
except ImportError:
|
||||
print("ovirt inventory script requires ovirt-engine-sdk-python")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class OVirtInventory(object):
|
||||
def __init__(self):
|
||||
# Read settings and parse CLI arguments
|
||||
self.args = self.parse_cli_args()
|
||||
self.driver = self.get_ovirt_driver()
|
||||
|
||||
# Just display data for specific host
|
||||
if self.args.host:
|
||||
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(
|
||||
data=self.group_instances(),
|
||||
pretty=self.args.pretty
|
||||
)
|
||||
)
|
||||
sys.exit(0)
|
||||
|
||||
@staticmethod
|
||||
def get_ovirt_driver():
|
||||
"""
|
||||
Determine the ovirt authorization settings and return a ovirt_sdk driver.
|
||||
|
||||
:rtype : ovirtsdk.api.API
|
||||
"""
|
||||
kwargs = {}
|
||||
|
||||
ovirt_ini_default_path = os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)), "ovirt.ini")
|
||||
ovirt_ini_path = os.environ.get('OVIRT_INI_PATH', ovirt_ini_default_path)
|
||||
|
||||
# Create a ConfigParser.
|
||||
# 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={
|
||||
'ovirt_url': '',
|
||||
'ovirt_username': '',
|
||||
'ovirt_password': '',
|
||||
'ovirt_api_secrets': '',
|
||||
})
|
||||
if 'ovirt' not in config.sections():
|
||||
config.add_section('ovirt')
|
||||
config.read(ovirt_ini_path)
|
||||
|
||||
# Attempt to get ovirt params from a configuration file, if one
|
||||
# exists.
|
||||
secrets_path = config.get('ovirt', 'ovirt_api_secrets')
|
||||
secrets_found = False
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences,PyPackageRequirements
|
||||
import secrets
|
||||
|
||||
kwargs = getattr(secrets, 'OVIRT_KEYWORD_PARAMS', {})
|
||||
secrets_found = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if not secrets_found and secrets_path:
|
||||
if not secrets_path.endswith('secrets.py'):
|
||||
err = "Must specify ovirt_sdk secrets file as /absolute/path/to/secrets.py"
|
||||
print(err)
|
||||
sys.exit(1)
|
||||
sys.path.append(os.path.dirname(secrets_path))
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences,PyPackageRequirements
|
||||
import secrets
|
||||
|
||||
kwargs = getattr(secrets, 'OVIRT_KEYWORD_PARAMS', {})
|
||||
except ImportError:
|
||||
pass
|
||||
if not secrets_found:
|
||||
kwargs = {
|
||||
'url': config.get('ovirt', 'ovirt_url'),
|
||||
'username': config.get('ovirt', 'ovirt_username'),
|
||||
'password': config.get('ovirt', 'ovirt_password'),
|
||||
}
|
||||
|
||||
# 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)
|
||||
|
||||
# Retrieve and return the ovirt driver.
|
||||
return API(insecure=True, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def parse_cli_args():
|
||||
"""
|
||||
Command line argument processing
|
||||
|
||||
:rtype : argparse.Namespace
|
||||
"""
|
||||
|
||||
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on ovirt')
|
||||
parser.add_argument('--list', action='store_true', default=True, help='List instances (default: True)')
|
||||
parser.add_argument('--host', action='store', help='Get all information about an instance')
|
||||
parser.add_argument('--pretty', action='store_true', default=False, help='Pretty format (default: False)')
|
||||
return parser.parse_args()
|
||||
|
||||
def node_to_dict(self, inst):
|
||||
"""
|
||||
:type inst: params.VM
|
||||
"""
|
||||
if inst is None:
|
||||
return {}
|
||||
|
||||
inst.get_custom_properties()
|
||||
ips = [ip.get_address() for ip in inst.get_guest_info().get_ips().get_ip()] \
|
||||
if inst.get_guest_info() is not None else []
|
||||
stats = {}
|
||||
for stat in inst.get_statistics().list():
|
||||
stats[stat.get_name()] = stat.get_values().get_value()[0].get_datum()
|
||||
|
||||
return {
|
||||
'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_ips': ips,
|
||||
'ovirt_name': inst.get_name(),
|
||||
'ovirt_description': inst.get_description(),
|
||||
'ovirt_status': inst.get_status().get_state(),
|
||||
'ovirt_zone': inst.get_cluster().get_id(),
|
||||
'ovirt_tags': self.get_tags(inst),
|
||||
'ovirt_stats': stats,
|
||||
# Hosts don't have a public name, so we add an IP
|
||||
'ansible_ssh_host': ips[0] if len(ips) > 0 else None
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_tags(inst):
|
||||
"""
|
||||
:type inst: params.VM
|
||||
"""
|
||||
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 """
|
||||
try:
|
||||
return self.driver.vms.get(name=instance_name)
|
||||
except Exception as e:
|
||||
return None
|
||||
|
||||
def group_instances(self):
|
||||
"""Group all instances"""
|
||||
groups = defaultdict(list)
|
||||
meta = {"hostvars": {}}
|
||||
|
||||
for node in self.driver.vms.list():
|
||||
assert isinstance(node, params.VM)
|
||||
name = node.get_name()
|
||||
|
||||
meta["hostvars"][name] = self.node_to_dict(node)
|
||||
|
||||
zone = node.get_cluster().get_name()
|
||||
groups[zone].append(name)
|
||||
|
||||
tags = self.get_tags(node)
|
||||
for t in tags:
|
||||
tag = 'tag_%s' % t
|
||||
groups[tag].append(name)
|
||||
|
||||
nets = [x.get_name() for x in node.get_nics().list()]
|
||||
for net in nets:
|
||||
net = 'network_%s' % net
|
||||
groups[net].append(name)
|
||||
|
||||
status = node.get_status().get_state()
|
||||
stat = 'status_%s' % status.lower()
|
||||
if stat in groups:
|
||||
groups[stat].append(name)
|
||||
else:
|
||||
groups[stat] = [name]
|
||||
|
||||
groups["_meta"] = meta
|
||||
|
||||
return groups
|
||||
|
||||
@staticmethod
|
||||
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)
|
||||
|
||||
# Run the script
|
||||
OVirtInventory()
|
||||
@ -1,239 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (C) 2014 Mathieu GAUTHIER-LAFAYE <gauthierl@lapth.cnrs.fr>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# 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:
|
||||
import simplejson as json
|
||||
import os
|
||||
import sys
|
||||
from optparse import OptionParser
|
||||
|
||||
from six import iteritems
|
||||
|
||||
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):
|
||||
def get_variables(self):
|
||||
variables = {}
|
||||
for key, value in iteritems(self):
|
||||
variables['proxmox_' + key] = value
|
||||
return variables
|
||||
|
||||
class ProxmoxVMList(list):
|
||||
def __init__(self, data=[]):
|
||||
for item in data:
|
||||
self.append(ProxmoxVM(item))
|
||||
|
||||
def get_names(self):
|
||||
return [vm['name'] for vm in self if vm['template'] != 1]
|
||||
|
||||
def get_by_name(self, name):
|
||||
results = [vm for vm in self if vm['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()
|
||||
|
||||
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
|
||||
self.credentials = None
|
||||
|
||||
if not options.url:
|
||||
raise Exception('Missing mandatory parameter --url (or PROXMOX_URL).')
|
||||
elif not options.username:
|
||||
raise Exception('Missing mandatory parameter --username (or PROXMOX_USERNAME).')
|
||||
elif not options.password:
|
||||
raise Exception('Missing mandatory parameter --password (or PROXMOX_PASSWORD).')
|
||||
|
||||
def auth(self):
|
||||
request_path = '{}api2/json/access/ticket'.format(self.options.url)
|
||||
|
||||
request_params = urllib.urlencode({
|
||||
'username': self.options.username,
|
||||
'password': self.options.password,
|
||||
})
|
||||
|
||||
data = json.load(open_url(request_path, data=request_params))
|
||||
|
||||
self.credentials = {
|
||||
'ticket': data['data']['ticket'],
|
||||
'CSRFPreventionToken': data['data']['CSRFPreventionToken'],
|
||||
}
|
||||
|
||||
def get(self, url, data=None):
|
||||
request_path = '{}{}'.format(self.options.url, url)
|
||||
|
||||
headers = {'Cookie': 'PVEAuthCookie={}'.format(self.credentials['ticket'])}
|
||||
request = open_url(request_path, data=data, headers=headers)
|
||||
|
||||
response = json.load(request)
|
||||
return response['data']
|
||||
|
||||
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')
|
||||
|
||||
def pools(self):
|
||||
return ProxmoxPoolList(self.get('api2/json/pools'))
|
||||
|
||||
def pool(self, poolid):
|
||||
return ProxmoxPool(self.get('api2/json/pools/{}'.format(poolid)))
|
||||
|
||||
def main_list(options):
|
||||
results = {
|
||||
'all': {
|
||||
'hosts': [],
|
||||
},
|
||||
'_meta': {
|
||||
'hostvars': {},
|
||||
}
|
||||
}
|
||||
|
||||
proxmox_api = ProxmoxAPI(options)
|
||||
proxmox_api.auth()
|
||||
|
||||
for node in proxmox_api.nodes().get_names():
|
||||
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():
|
||||
results[pool] = {
|
||||
'hosts': proxmox_api.pool(pool).get_members_name(),
|
||||
}
|
||||
|
||||
return results
|
||||
|
||||
def main_host(options):
|
||||
proxmox_api = ProxmoxAPI(options)
|
||||
proxmox_api.auth()
|
||||
|
||||
for node in proxmox_api.nodes().get_names():
|
||||
qemu_list = proxmox_api.node_qemu(node)
|
||||
qemu = qemu_list.get_by_name(options.host)
|
||||
if qemu:
|
||||
return qemu.get_variables()
|
||||
|
||||
return {}
|
||||
|
||||
def main():
|
||||
parser = OptionParser(usage='%prog [options] --list | --host HOSTNAME')
|
||||
parser.add_option('--list', action="store_true", default=False, dest="list")
|
||||
parser.add_option('--host', dest="host")
|
||||
parser.add_option('--url', default=os.environ.get('PROXMOX_URL'), dest='url')
|
||||
parser.add_option('--username', default=os.environ.get('PROXMOX_USERNAME'), dest='username')
|
||||
parser.add_option('--password', default=os.environ.get('PROXMOX_PASSWORD'), dest='password')
|
||||
parser.add_option('--pretty', action="store_true", default=False, dest='pretty')
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if options.list:
|
||||
data = main_list(options)
|
||||
elif options.host:
|
||||
data = main_host(options)
|
||||
else:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
indent = None
|
||||
if options.pretty:
|
||||
indent = 2
|
||||
|
||||
print(json.dumps(data, indent=indent))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -1,80 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import json
|
||||
import requests
|
||||
import os
|
||||
import argparse
|
||||
import types
|
||||
|
||||
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
|
||||
@ -1,66 +0,0 @@
|
||||
# Ansible Rackspace external inventory script settings
|
||||
#
|
||||
|
||||
[rax]
|
||||
|
||||
# Environment Variable: RAX_CREDS_FILE
|
||||
#
|
||||
# An optional configuration that points to a pyrax-compatible credentials
|
||||
# file.
|
||||
#
|
||||
# If not supplied, rax.py will look for a credentials file
|
||||
# at ~/.rackspace_cloud_credentials. It uses the Rackspace Python SDK,
|
||||
# and therefore requires a file formatted per the SDK's specifications.
|
||||
#
|
||||
# https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md
|
||||
# creds_file = ~/.rackspace_cloud_credentials
|
||||
|
||||
# Environment Variable: RAX_REGION
|
||||
#
|
||||
# An optional environment variable to narrow inventory search
|
||||
# scope. If used, needs a value like ORD, DFW, SYD (a Rackspace
|
||||
# datacenter) and optionally accepts a comma-separated list.
|
||||
# regions = IAD,ORD,DFW
|
||||
|
||||
# Environment Variable: RAX_ENV
|
||||
#
|
||||
# A configuration that will use an environment as configured in
|
||||
# ~/.pyrax.cfg, see
|
||||
# https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md
|
||||
# env = prod
|
||||
|
||||
# Environment Variable: RAX_META_PREFIX
|
||||
# Default: meta
|
||||
#
|
||||
# A configuration that changes the prefix used for meta key/value groups.
|
||||
# For compatibility with ec2.py set to "tag"
|
||||
# meta_prefix = meta
|
||||
|
||||
# Environment Variable: RAX_ACCESS_NETWORK
|
||||
# Default: public
|
||||
#
|
||||
# A configuration that will tell the inventory script to use a specific
|
||||
# server network to determine the ansible_ssh_host value. If no address
|
||||
# is found, ansible_ssh_host will not be set. Accepts a comma-separated
|
||||
# list of network names, the first found wins.
|
||||
# access_network = public
|
||||
|
||||
# Environment Variable: RAX_ACCESS_IP_VERSION
|
||||
# Default: 4
|
||||
#
|
||||
# A configuration related to "access_network" that will attempt to
|
||||
# determine the ansible_ssh_host value for either IPv4 or IPv6. If no
|
||||
# address is found, ansible_ssh_host will not be set.
|
||||
# Acceptable values are: 4 or 6. Values other than 4 or 6
|
||||
# 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
|
||||
@ -1,451 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# (c) 2013, Jesse Keating <jesse.keating@rackspace.com,
|
||||
# Paul Durivage <paul.durivage@rackspace.com>,
|
||||
# Matt Martz <matt@sivel.net>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
"""
|
||||
Rackspace Cloud Inventory
|
||||
|
||||
Authors:
|
||||
Jesse Keating <jesse.keating@rackspace.com,
|
||||
Paul Durivage <paul.durivage@rackspace.com>,
|
||||
Matt Martz <matt@sivel.net>
|
||||
|
||||
|
||||
Description:
|
||||
Generates inventory that Ansible can understand by making API request to
|
||||
Rackspace Public Cloud API
|
||||
|
||||
When run against a specific host, this script returns variables similar to:
|
||||
rax_os-ext-sts_task_state
|
||||
rax_addresses
|
||||
rax_links
|
||||
rax_image
|
||||
rax_os-ext-sts_vm_state
|
||||
rax_flavor
|
||||
rax_id
|
||||
rax_rax-bandwidth_bandwidth
|
||||
rax_user_id
|
||||
rax_os-dcf_diskconfig
|
||||
rax_accessipv4
|
||||
rax_accessipv6
|
||||
rax_progress
|
||||
rax_os-ext-sts_power_state
|
||||
rax_metadata
|
||||
rax_status
|
||||
rax_updated
|
||||
rax_hostid
|
||||
rax_name
|
||||
rax_created
|
||||
rax_tenant_id
|
||||
rax_loaded
|
||||
|
||||
Configuration:
|
||||
rax.py can be configured using a rax.ini file or via environment
|
||||
variables. The rax.ini file should live in the same directory along side
|
||||
this script.
|
||||
|
||||
The section header for configuration values related to this
|
||||
inventory plugin is [rax]
|
||||
|
||||
[rax]
|
||||
creds_file = ~/.rackspace_cloud_credentials
|
||||
regions = IAD,ORD,DFW
|
||||
env = prod
|
||||
meta_prefix = meta
|
||||
access_network = public
|
||||
access_ip_version = 4
|
||||
|
||||
Each of these configurations also has a corresponding environment variable.
|
||||
An environment variable will override a configuration file value.
|
||||
|
||||
creds_file:
|
||||
Environment Variable: RAX_CREDS_FILE
|
||||
|
||||
An optional configuration that points to a pyrax-compatible credentials
|
||||
file.
|
||||
|
||||
If not supplied, rax.py will look for a credentials file
|
||||
at ~/.rackspace_cloud_credentials. It uses the Rackspace Python SDK,
|
||||
and therefore requires a file formatted per the SDK's specifications.
|
||||
|
||||
https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md
|
||||
|
||||
regions:
|
||||
Environment Variable: RAX_REGION
|
||||
|
||||
An optional environment variable to narrow inventory search
|
||||
scope. If used, needs a value like ORD, DFW, SYD (a Rackspace
|
||||
datacenter) and optionally accepts a comma-separated list.
|
||||
|
||||
environment:
|
||||
Environment Variable: RAX_ENV
|
||||
|
||||
A configuration that will use an environment as configured in
|
||||
~/.pyrax.cfg, see
|
||||
https://github.com/rackspace/pyrax/blob/master/docs/getting_started.md
|
||||
|
||||
meta_prefix:
|
||||
Environment Variable: RAX_META_PREFIX
|
||||
Default: meta
|
||||
|
||||
A configuration that changes the prefix used for meta key/value groups.
|
||||
For compatibility with ec2.py set to "tag"
|
||||
|
||||
access_network:
|
||||
Environment Variable: RAX_ACCESS_NETWORK
|
||||
Default: public
|
||||
|
||||
A configuration that will tell the inventory script to use a specific
|
||||
server network to determine the ansible_ssh_host value. If no address
|
||||
is found, ansible_ssh_host will not be set. Accepts a comma-separated
|
||||
list of network names, the first found wins.
|
||||
|
||||
access_ip_version:
|
||||
Environment Variable: RAX_ACCESS_IP_VERSION
|
||||
Default: 4
|
||||
|
||||
A configuration related to "access_network" that will attempt to
|
||||
determine the ansible_ssh_host value for either IPv4 or IPv6. If no
|
||||
address is found, ansible_ssh_host will not be set.
|
||||
Acceptable values are: 4 or 6. Values other than 4 or 6
|
||||
will be ignored, and 4 will be used. Accepts a comma-separated list,
|
||||
the first found wins.
|
||||
|
||||
Examples:
|
||||
List server instances
|
||||
$ RAX_CREDS_FILE=~/.raxpub rax.py --list
|
||||
|
||||
List servers in ORD datacenter only
|
||||
$ RAX_CREDS_FILE=~/.raxpub RAX_REGION=ORD rax.py --list
|
||||
|
||||
List servers in ORD and DFW datacenters
|
||||
$ RAX_CREDS_FILE=~/.raxpub RAX_REGION=ORD,DFW rax.py --list
|
||||
|
||||
Get server details for server named "server.example.com"
|
||||
$ RAX_CREDS_FILE=~/.raxpub rax.py --host server.example.com
|
||||
|
||||
Use the instance private IP to connect (instead of public IP)
|
||||
$ RAX_CREDS_FILE=~/.raxpub RAX_ACCESS_NETWORK=private rax.py --list
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import argparse
|
||||
import warnings
|
||||
import collections
|
||||
import ConfigParser
|
||||
|
||||
from six import iteritems
|
||||
|
||||
from ansible.constants import get_config, mk_boolean
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
try:
|
||||
import pyrax
|
||||
from pyrax.utils import slugify
|
||||
except ImportError:
|
||||
sys.exit('pyrax is required for this module')
|
||||
|
||||
from time import time
|
||||
|
||||
|
||||
NON_CALLABLES = (basestring, bool, dict, int, list, type(None))
|
||||
|
||||
|
||||
def load_config_file():
|
||||
p = ConfigParser.ConfigParser()
|
||||
config_file = os.path.join(os.path.dirname(os.path.realpath(__file__)),
|
||||
'rax.ini')
|
||||
try:
|
||||
p.read(config_file)
|
||||
except ConfigParser.Error:
|
||||
return None
|
||||
else:
|
||||
return p
|
||||
p = load_config_file()
|
||||
|
||||
|
||||
def rax_slugify(value):
|
||||
return 'rax_%s' % (re.sub('[^\w-]', '_', value).lower().lstrip('_'))
|
||||
|
||||
|
||||
def to_dict(obj):
|
||||
instance = {}
|
||||
for key in dir(obj):
|
||||
value = getattr(obj, key)
|
||||
if isinstance(value, NON_CALLABLES) and not key.startswith('_'):
|
||||
key = rax_slugify(key)
|
||||
instance[key] = value
|
||||
|
||||
return instance
|
||||
|
||||
|
||||
def host(regions, hostname):
|
||||
hostvars = {}
|
||||
|
||||
for region in regions:
|
||||
# Connect to the region
|
||||
cs = pyrax.connect_to_cloudservers(region=region)
|
||||
for server in cs.servers.list():
|
||||
if server.name == hostname:
|
||||
for key, value in to_dict(server).items():
|
||||
hostvars[key] = value
|
||||
|
||||
# And finally, add an IP address
|
||||
hostvars['ansible_ssh_host'] = server.accessIPv4
|
||||
print(json.dumps(hostvars, sort_keys=True, indent=4))
|
||||
|
||||
|
||||
def _list_into_cache(regions):
|
||||
groups = collections.defaultdict(list)
|
||||
hostvars = collections.defaultdict(dict)
|
||||
images = {}
|
||||
cbs_attachments = collections.defaultdict(dict)
|
||||
|
||||
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:
|
||||
ip_versions = map(int, get_config(p, 'rax', 'access_ip_version',
|
||||
'RAX_ACCESS_IP_VERSION', 4,
|
||||
islist=True))
|
||||
except:
|
||||
ip_versions = [4]
|
||||
else:
|
||||
ip_versions = [v for v in ip_versions if v in [4, 6]]
|
||||
if not ip_versions:
|
||||
ip_versions = [4]
|
||||
|
||||
# Go through all the regions looking for servers
|
||||
for region in regions:
|
||||
# Connect to the region
|
||||
cs = pyrax.connect_to_cloudservers(region=region)
|
||||
if cs is None:
|
||||
warnings.warn(
|
||||
'Connecting to Rackspace region "%s" has caused Pyrax to '
|
||||
'return None. Is this a valid region?' % region,
|
||||
RuntimeWarning)
|
||||
continue
|
||||
for server in cs.servers.list():
|
||||
# Create a group on region
|
||||
groups[region].append(server.name)
|
||||
|
||||
# Check if group metadata key in servers' metadata
|
||||
group = server.metadata.get('group')
|
||||
if group:
|
||||
groups[group].append(server.name)
|
||||
|
||||
for extra_group in server.metadata.get('groups', '').split(','):
|
||||
if extra_group:
|
||||
groups[extra_group].append(server.name)
|
||||
|
||||
# Add host metadata
|
||||
for key, value in to_dict(server).items():
|
||||
hostvars[server.name][key] = value
|
||||
|
||||
hostvars[server.name]['rax_region'] = region
|
||||
|
||||
for key, value in iteritems(server.metadata):
|
||||
groups['%s_%s_%s' % (prefix, key, value)].append(server.name)
|
||||
|
||||
groups['instance-%s' % server.id].append(server.name)
|
||||
groups['flavor-%s' % server.flavor['id']].append(server.name)
|
||||
|
||||
# Handle boot from volume
|
||||
if not server.image:
|
||||
if not cbs_attachments[region]:
|
||||
cbs = pyrax.connect_to_cloud_blockstorage(region)
|
||||
for vol in cbs.list():
|
||||
if mk_boolean(vol.bootable):
|
||||
for attachment in vol.attachments:
|
||||
metadata = vol.volume_image_metadata
|
||||
server_id = attachment['server_id']
|
||||
cbs_attachments[region][server_id] = {
|
||||
'id': metadata['image_id'],
|
||||
'name': slugify(metadata['image_name'])
|
||||
}
|
||||
image = cbs_attachments[region].get(server.id)
|
||||
if image:
|
||||
server.image = {'id': image['id']}
|
||||
hostvars[server.name]['rax_image'] = server.image
|
||||
hostvars[server.name]['rax_boot_source'] = 'volume'
|
||||
images[image['id']] = image['name']
|
||||
else:
|
||||
hostvars[server.name]['rax_boot_source'] = 'local'
|
||||
|
||||
try:
|
||||
imagegroup = 'image-%s' % images[server.image['id']]
|
||||
groups[imagegroup].append(server.name)
|
||||
groups['image-%s' % server.image['id']].append(server.name)
|
||||
except KeyError:
|
||||
try:
|
||||
image = cs.images.get(server.image['id'])
|
||||
except cs.exceptions.NotFound:
|
||||
groups['image-%s' % server.image['id']].append(server.name)
|
||||
else:
|
||||
images[image.id] = image.human_id
|
||||
groups['image-%s' % image.human_id].append(server.name)
|
||||
groups['image-%s' % server.image['id']].append(server.name)
|
||||
|
||||
# And finally, add an IP address
|
||||
ansible_ssh_host = None
|
||||
# use accessIPv[46] instead of looping address for 'public'
|
||||
for network_name in networks:
|
||||
if ansible_ssh_host:
|
||||
break
|
||||
if network_name == 'public':
|
||||
for version_name in ip_versions:
|
||||
if ansible_ssh_host:
|
||||
break
|
||||
if version_name == 6 and server.accessIPv6:
|
||||
ansible_ssh_host = server.accessIPv6
|
||||
elif server.accessIPv4:
|
||||
ansible_ssh_host = server.accessIPv4
|
||||
if not ansible_ssh_host:
|
||||
addresses = server.addresses.get(network_name, [])
|
||||
for address in addresses:
|
||||
for version_name in ip_versions:
|
||||
if ansible_ssh_host:
|
||||
break
|
||||
if address.get('version') == version_name:
|
||||
ansible_ssh_host = address.get('addr')
|
||||
break
|
||||
if ansible_ssh_host:
|
||||
hostvars[server.name]['ansible_ssh_host'] = ansible_ssh_host
|
||||
|
||||
if hostvars:
|
||||
groups['_meta'] = {'hostvars': hostvars}
|
||||
|
||||
with open(get_cache_file_path(regions), 'w') as cache_file:
|
||||
json.dump(groups, cache_file)
|
||||
|
||||
|
||||
def get_cache_file_path(regions):
|
||||
regions_str = '.'.join([reg.strip().lower() for reg in regions])
|
||||
ansible_tmp_path = os.path.join(os.path.expanduser("~"), '.ansible', 'tmp')
|
||||
if not os.path.exists(ansible_tmp_path):
|
||||
os.makedirs(ansible_tmp_path)
|
||||
return os.path.join(ansible_tmp_path,
|
||||
'ansible-rax-%s-%s.cache' % (
|
||||
pyrax.identity.username, regions_str))
|
||||
|
||||
|
||||
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):
|
||||
# Cache file doesn't exist or older than 10m or refresh cache requested
|
||||
_list_into_cache(regions)
|
||||
|
||||
with open(get_cache_file_path(regions), 'r') as cache_file:
|
||||
groups = json.load(cache_file)
|
||||
print(json.dumps(groups, sort_keys=True, indent=4))
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description='Ansible Rackspace Cloud '
|
||||
'inventory module')
|
||||
group = parser.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument('--list', action='store_true',
|
||||
help='List active servers')
|
||||
group.add_argument('--host', help='List details about the specific host')
|
||||
parser.add_argument('--refresh-cache', action='store_true', default=False,
|
||||
help=('Force refresh of cache, making API requests to'
|
||||
'RackSpace (default: False - use cache files)'))
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def setup():
|
||||
default_creds_file = os.path.expanduser('~/.rackspace_cloud_credentials')
|
||||
|
||||
env = get_config(p, 'rax', 'environment', 'RAX_ENV', None)
|
||||
if env:
|
||||
pyrax.set_environment(env)
|
||||
|
||||
keyring_username = pyrax.get_setting('keyring_username')
|
||||
|
||||
# Attempt to grab credentials from environment first
|
||||
creds_file = get_config(p, 'rax', 'creds_file',
|
||||
'RAX_CREDS_FILE', None)
|
||||
if creds_file is not None:
|
||||
creds_file = os.path.expanduser(creds_file)
|
||||
else:
|
||||
# But if that fails, use the default location of
|
||||
# ~/.rackspace_cloud_credentials
|
||||
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))
|
||||
|
||||
identity_type = pyrax.get_setting('identity_type')
|
||||
pyrax.set_setting('identity_type', identity_type or 'rackspace')
|
||||
|
||||
region = pyrax.get_setting('region')
|
||||
|
||||
try:
|
||||
if keyring_username:
|
||||
pyrax.keyring_auth(keyring_username, region=region)
|
||||
else:
|
||||
pyrax.set_credential_file(creds_file, region=region)
|
||||
except Exception as e:
|
||||
sys.exit("%s: %s" % (e, e.message))
|
||||
|
||||
regions = []
|
||||
if region:
|
||||
regions.append(region)
|
||||
else:
|
||||
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)
|
||||
elif region not in regions:
|
||||
regions.append(region)
|
||||
|
||||
return regions
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
regions = setup()
|
||||
if args.list:
|
||||
_list(regions, refresh_cache=args.refresh_cache)
|
||||
elif args.host:
|
||||
host(regions, args.host)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -1,35 +0,0 @@
|
||||
# Rudder external inventory script settings
|
||||
#
|
||||
|
||||
[rudder]
|
||||
|
||||
# Your Rudder server API URL, typically:
|
||||
# https://rudder.local/rudder/api
|
||||
uri = https://rudder.local/rudder/api
|
||||
|
||||
# By default, Rudder uses a self-signed certificate. Set this to True
|
||||
# to disable certificate validation.
|
||||
disable_ssl_certificate_validation = True
|
||||
|
||||
# Your Rudder API token, created in the Web interface.
|
||||
token = aaabbbccc
|
||||
|
||||
# Rudder API version to use, use "latest" for lastest available
|
||||
# version.
|
||||
version = latest
|
||||
|
||||
# Property to use as group name in the output.
|
||||
# Can generally be "id" or "displayName".
|
||||
group_name = displayName
|
||||
|
||||
# Fail if there are two groups with the same name or two hosts with the
|
||||
# same hostname in the output.
|
||||
fail_if_name_collision = True
|
||||
|
||||
# We cache the results of Rudder API in a local file
|
||||
cache_path = /tmp/ansible-rudder.cache
|
||||
|
||||
# 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.
|
||||
# Set to 0 to disable cache.
|
||||
cache_max_age = 500
|
||||
@ -1,302 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2015, Normation SAS
|
||||
#
|
||||
# Inspired by the EC2 inventory plugin:
|
||||
# https://github.com/ansible/ansible/blob/devel/contrib/inventory/ec2.py
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
######################################################################
|
||||
|
||||
'''
|
||||
Rudder external inventory script
|
||||
=================================
|
||||
|
||||
Generates inventory that Ansible can understand by making API request to
|
||||
a Rudder server. This script is compatible with Rudder 2.10 or later.
|
||||
|
||||
The output JSON includes all your Rudder groups, containing the hostnames of
|
||||
their nodes. Groups and nodes have a variable called rudder_group_id and
|
||||
rudder_node_id, which is the Rudder internal id of the item, allowing to identify
|
||||
them uniquely. Hosts variables also include your node properties, which are
|
||||
key => value properties set by the API and specific to each node.
|
||||
|
||||
This script assumes there is an rudder.ini file alongside it. To specify a
|
||||
different path to rudder.ini, define the RUDDER_INI_PATH environment variable:
|
||||
|
||||
export RUDDER_INI_PATH=/path/to/my_rudder.ini
|
||||
|
||||
You have to configure your Rudder server information, either in rudder.ini or
|
||||
by overriding it with environment variables:
|
||||
|
||||
export RUDDER_API_VERSION='latest'
|
||||
export RUDDER_API_TOKEN='my_token'
|
||||
export RUDDER_API_URI='https://rudder.local/rudder/api'
|
||||
'''
|
||||
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import argparse
|
||||
import six
|
||||
import httplib2 as http
|
||||
from time import time
|
||||
from six.moves import configparser
|
||||
|
||||
try:
|
||||
from urlparse import urlparse
|
||||
except ImportError:
|
||||
from urllib.parse import urlparse
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
|
||||
class RudderInventory(object):
|
||||
def __init__(self):
|
||||
''' Main execution path '''
|
||||
|
||||
# Empty inventory by default
|
||||
self.inventory = {}
|
||||
|
||||
# Read settings and parse CLI arguments
|
||||
self.read_settings()
|
||||
self.parse_cli_args()
|
||||
|
||||
# Create connection
|
||||
self.conn = http.Http(disable_ssl_certificate_validation=self.disable_ssl_validation)
|
||||
|
||||
# Cache
|
||||
if self.args.refresh_cache:
|
||||
self.update_cache()
|
||||
elif not self.is_cache_valid():
|
||||
self.update_cache()
|
||||
else:
|
||||
self.load_cache()
|
||||
|
||||
data_to_print = {}
|
||||
|
||||
if self.args.host:
|
||||
data_to_print = self.get_host_info(self.args.host)
|
||||
elif self.args.list:
|
||||
data_to_print = self.get_list_info()
|
||||
|
||||
print(self.json_format_dict(data_to_print, True))
|
||||
|
||||
def read_settings(self):
|
||||
''' Reads the settings from the rudder.ini file '''
|
||||
if six.PY2:
|
||||
config = configparser.SafeConfigParser()
|
||||
else:
|
||||
config = configparser.ConfigParser()
|
||||
rudder_default_ini_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'rudder.ini')
|
||||
rudder_ini_path = os.path.expanduser(os.path.expandvars(os.environ.get('RUDDER_INI_PATH', rudder_default_ini_path)))
|
||||
config.read(rudder_ini_path)
|
||||
|
||||
self.token = os.environ.get('RUDDER_API_TOKEN', config.get('rudder', 'token'))
|
||||
self.version = os.environ.get('RUDDER_API_VERSION', config.get('rudder', 'version'))
|
||||
self.uri = os.environ.get('RUDDER_API_URI', config.get('rudder', 'uri'))
|
||||
|
||||
self.disable_ssl_validation = config.getboolean('rudder', 'disable_ssl_certificate_validation')
|
||||
self.group_name = config.get('rudder', 'group_name')
|
||||
self.fail_if_name_collision = config.getboolean('rudder', 'fail_if_name_collision')
|
||||
|
||||
self.cache_path = config.get('rudder', 'cache_path')
|
||||
self.cache_max_age = config.getint('rudder', 'cache_max_age')
|
||||
|
||||
def parse_cli_args(self):
|
||||
''' Command line argument processing '''
|
||||
|
||||
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on Rudder inventory')
|
||||
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 Rudder (default: False - use cache files)')
|
||||
self.args = parser.parse_args()
|
||||
|
||||
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):
|
||||
mod_time = os.path.getmtime(self.cache_path)
|
||||
current_time = time()
|
||||
if (mod_time + self.cache_max_age) > current_time:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def load_cache(self):
|
||||
''' Reads the cache from the cache file sets self.cache '''
|
||||
|
||||
cache = open(self.cache_path, 'r')
|
||||
json_cache = cache.read()
|
||||
|
||||
try:
|
||||
self.inventory = json.loads(json_cache)
|
||||
except ValueError as e:
|
||||
self.fail_with_error('Could not parse JSON response from local cache', 'parsing local cache')
|
||||
|
||||
def write_cache(self):
|
||||
''' Writes data in JSON format to a file '''
|
||||
|
||||
json_data = self.json_format_dict(self.inventory, True)
|
||||
cache = open(self.cache_path, 'w')
|
||||
cache.write(json_data)
|
||||
cache.close()
|
||||
|
||||
def get_nodes(self):
|
||||
''' Gets the nodes list from Rudder '''
|
||||
|
||||
path = '/nodes?select=nodeAndPolicyServer'
|
||||
result = self.api_call(path)
|
||||
|
||||
nodes = {}
|
||||
|
||||
for node in result['data']['nodes']:
|
||||
nodes[node['id']] = {}
|
||||
nodes[node['id']]['hostname'] = node['hostname']
|
||||
if 'properties' in node:
|
||||
nodes[node['id']]['properties'] = node['properties']
|
||||
else:
|
||||
nodes[node['id']]['properties'] = []
|
||||
|
||||
return nodes
|
||||
|
||||
def get_groups(self):
|
||||
''' Gets the groups list from Rudder '''
|
||||
|
||||
path = '/groups'
|
||||
result = self.api_call(path)
|
||||
|
||||
groups = {}
|
||||
|
||||
for group in result['data']['groups']:
|
||||
groups[group['id']] = {'hosts': group['nodeIds'], 'name': self.to_safe(group[self.group_name])}
|
||||
|
||||
return groups
|
||||
|
||||
def update_cache(self):
|
||||
''' Fetches the inventory information from Rudder and creates the inventory '''
|
||||
|
||||
nodes = self.get_nodes()
|
||||
groups = self.get_groups()
|
||||
|
||||
inventory = {}
|
||||
|
||||
for group in groups:
|
||||
# Check for name collision
|
||||
if self.fail_if_name_collision:
|
||||
if groups[group]['name'] in inventory:
|
||||
self.fail_with_error('Name collision on groups: "%s" appears twice' % groups[group]['name'], 'creating groups')
|
||||
# Add group to inventory
|
||||
inventory[groups[group]['name']] = {}
|
||||
inventory[groups[group]['name']]['hosts'] = []
|
||||
inventory[groups[group]['name']]['vars'] = {}
|
||||
inventory[groups[group]['name']]['vars']['rudder_group_id'] = group
|
||||
for node in groups[group]['hosts']:
|
||||
# Add node to group
|
||||
inventory[groups[group]['name']]['hosts'].append(nodes[node]['hostname'])
|
||||
|
||||
properties = {}
|
||||
|
||||
for node in nodes:
|
||||
# Check for name collision
|
||||
if self.fail_if_name_collision:
|
||||
if nodes[node]['hostname'] in properties:
|
||||
self.fail_with_error('Name collision on hosts: "%s" appears twice' % nodes[node]['hostname'], 'creating hosts')
|
||||
# Add node properties to inventory
|
||||
properties[nodes[node]['hostname']] = {}
|
||||
properties[nodes[node]['hostname']]['rudder_node_id'] = node
|
||||
for node_property in nodes[node]['properties']:
|
||||
properties[nodes[node]['hostname']][self.to_safe(node_property['name'])] = node_property['value']
|
||||
|
||||
inventory['_meta'] = {}
|
||||
inventory['_meta']['hostvars'] = properties
|
||||
|
||||
self.inventory = inventory
|
||||
|
||||
if self.cache_max_age > 0:
|
||||
self.write_cache()
|
||||
|
||||
def get_list_info(self):
|
||||
''' Gets inventory information from local cache '''
|
||||
|
||||
return self.inventory
|
||||
|
||||
def get_host_info(self, hostname):
|
||||
''' Gets information about a specific host from local cache '''
|
||||
|
||||
if hostname in self.inventory['_meta']['hostvars']:
|
||||
return self.inventory['_meta']['hostvars'][hostname]
|
||||
else:
|
||||
return {}
|
||||
|
||||
def api_call(self, path):
|
||||
''' Performs an API request '''
|
||||
|
||||
headers = {
|
||||
'X-API-Token': self.token,
|
||||
'X-API-Version': self.version,
|
||||
'Content-Type': 'application/json;charset=utf-8'
|
||||
}
|
||||
|
||||
target = urlparse(self.uri + path)
|
||||
method = 'GET'
|
||||
body = ''
|
||||
|
||||
try:
|
||||
response, content = self.conn.request(target.geturl(), method, body, headers)
|
||||
except:
|
||||
self.fail_with_error('Error connecting to Rudder server')
|
||||
|
||||
try:
|
||||
data = json.loads(content)
|
||||
except ValueError as e:
|
||||
self.fail_with_error('Could not parse JSON response from Rudder API', 'reading API response')
|
||||
|
||||
return data
|
||||
|
||||
def fail_with_error(self, err_msg, err_operation=None):
|
||||
''' Logs an error to std err for ansible-playbook to consume and exit '''
|
||||
if err_operation:
|
||||
err_msg = 'ERROR: "{err_msg}", while: {err_operation}'.format(
|
||||
err_msg=err_msg, err_operation=err_operation)
|
||||
sys.stderr.write(err_msg)
|
||||
sys.exit(1)
|
||||
|
||||
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 to_safe(self, word):
|
||||
''' Converts 'bad' characters in a string to underscores so they can be
|
||||
used as Ansible variable names '''
|
||||
|
||||
return re.sub('[^A-Za-z0-9\_]', '_', word)
|
||||
|
||||
# Run the script
|
||||
RudderInventory()
|
||||
@ -1,115 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# (c) 2015, Marc Abramowitz <marca@surveymonkey.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/>.
|
||||
|
||||
# Dynamic inventory script which lets you use nodes discovered by Serf
|
||||
# (https://serfdom.io/).
|
||||
#
|
||||
# Requires the `serfclient` Python module from
|
||||
# https://pypi.python.org/pypi/serfclient
|
||||
#
|
||||
# Environment variables
|
||||
# ---------------------
|
||||
# - `SERF_RPC_ADDR`
|
||||
# - `SERF_RPC_AUTH`
|
||||
#
|
||||
# These variables are described at https://www.serfdom.io/docs/commands/members.html#_rpc_addr
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
import os
|
||||
import sys
|
||||
|
||||
# https://pypi.python.org/pypi/serfclient
|
||||
from serfclient import SerfClient, EnvironmentConfig
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
_key = 'serf'
|
||||
|
||||
|
||||
def _serf_client():
|
||||
env = EnvironmentConfig()
|
||||
return SerfClient(host=env.host, port=env.port, rpc_auth=env.auth_key)
|
||||
|
||||
|
||||
def get_serf_members_data():
|
||||
return _serf_client().members().body['Members']
|
||||
|
||||
|
||||
def get_nodes(data):
|
||||
return [node['Name'] for node in data]
|
||||
|
||||
|
||||
def get_groups(data):
|
||||
groups = collections.defaultdict(list)
|
||||
|
||||
for node in data:
|
||||
for key, value in node['Tags'].items():
|
||||
groups[value].append(node['Name'])
|
||||
|
||||
return groups
|
||||
|
||||
|
||||
def get_meta(data):
|
||||
meta = {'hostvars': {}}
|
||||
for node in data:
|
||||
meta['hostvars'][node['Name']] = node['Tags']
|
||||
return meta
|
||||
|
||||
|
||||
def print_list():
|
||||
data = get_serf_members_data()
|
||||
nodes = get_nodes(data)
|
||||
groups = get_groups(data)
|
||||
meta = get_meta(data)
|
||||
inventory_data = {_key: nodes, '_meta': meta}
|
||||
inventory_data.update(groups)
|
||||
print(json.dumps(inventory_data))
|
||||
|
||||
|
||||
def print_host(host):
|
||||
data = get_serf_members_data()
|
||||
meta = get_meta(data)
|
||||
print(json.dumps(meta['hostvars'][host]))
|
||||
|
||||
|
||||
def get_args(args_list):
|
||||
parser = argparse.ArgumentParser(
|
||||
description='ansible inventory script reading from serf cluster')
|
||||
mutex_group = parser.add_mutually_exclusive_group(required=True)
|
||||
help_list = 'list all hosts from serf cluster'
|
||||
mutex_group.add_argument('--list', action='store_true', help=help_list)
|
||||
help_host = 'display variables for a host'
|
||||
mutex_group.add_argument('--host', help=help_host)
|
||||
return parser.parse_args(args_list)
|
||||
|
||||
|
||||
def main(args_list):
|
||||
args = get_args(args_list)
|
||||
if args.list:
|
||||
print_list()
|
||||
if args.host:
|
||||
print_host(args.host)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
||||
@ -1,16 +0,0 @@
|
||||
# Put this ini-file in the same directory as spacewalk.py
|
||||
# Command line options have precedence over options defined in here.
|
||||
|
||||
[spacewalk]
|
||||
# To limit the script on one organization in spacewalk, uncomment org_number
|
||||
# and fill in the organization ID:
|
||||
# org_number=2
|
||||
|
||||
# To prefix the group names with the organization ID set prefix_org_name=true.
|
||||
# This is convenient when org_number is not set and you have the same group names
|
||||
# in multiple organizations within spacewalk
|
||||
# The prefix is "org_number-"
|
||||
prefix_org_name=false
|
||||
|
||||
# Default cache_age for files created with spacewalk-report is 300sec.
|
||||
cache_age=300
|
||||
@ -1,235 +0,0 @@
|
||||
#!/bin/env python
|
||||
|
||||
"""
|
||||
Spacewalk external inventory script
|
||||
=================================
|
||||
|
||||
Ansible has a feature where instead of reading from /etc/ansible/hosts
|
||||
as a text file, it can query external programs to obtain the list
|
||||
of hosts, groups the hosts are in, and even variables to assign to each host.
|
||||
|
||||
To use this, copy this file over /etc/ansible/hosts and chmod +x the file.
|
||||
This, more or less, allows you to keep one central database containing
|
||||
info about all of your managed instances.
|
||||
|
||||
This script is dependent upon the spacealk-reports package being installed
|
||||
on the same machine. It is basically a CSV-to-JSON converter from the
|
||||
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
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 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
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
from optparse import OptionParser
|
||||
import subprocess
|
||||
import ConfigParser
|
||||
|
||||
from six import iteritems
|
||||
|
||||
try:
|
||||
import json
|
||||
except:
|
||||
import simplejson as json
|
||||
|
||||
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.join(base_dir, "spacewalk.ini")
|
||||
|
||||
# Sanity check
|
||||
if not os.path.exists(SW_REPORT):
|
||||
print('Error: %s is required for operation.' % (SW_REPORT), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Pre-startup work
|
||||
if not os.path.exists(CACHE_DIR):
|
||||
os.mkdir(CACHE_DIR)
|
||||
os.chmod(CACHE_DIR, 0o2775)
|
||||
|
||||
# Helper functions
|
||||
#------------------------------
|
||||
|
||||
def spacewalk_report(name):
|
||||
"""Yield a dictionary form of each CSV output produced by the specified
|
||||
spacewalk-report
|
||||
"""
|
||||
cache_filename = os.path.join(CACHE_DIR, name)
|
||||
if not os.path.exists(cache_filename) or \
|
||||
(time.time() - os.stat(cache_filename).st_mtime) > CACHE_AGE:
|
||||
# Update the cache
|
||||
fh = open(cache_filename, 'w')
|
||||
p = subprocess.Popen([SW_REPORT, name], stdout=fh)
|
||||
p.wait()
|
||||
fh.close()
|
||||
|
||||
lines = open(cache_filename, 'r').readlines()
|
||||
keys = lines[0].strip().split(',')
|
||||
# add 'spacewalk_' prefix to the keys
|
||||
keys = [ 'spacewalk_' + key for key in keys ]
|
||||
for line in lines[1:]:
|
||||
values = line.strip().split(',')
|
||||
if len(keys) == len(values):
|
||||
yield dict(zip(keys, values))
|
||||
|
||||
|
||||
# Options
|
||||
#------------------------------
|
||||
|
||||
parser = OptionParser(usage="%prog [options] --list | --host <machine>")
|
||||
parser.add_option('--list', default=False, dest="list", action="store_true",
|
||||
help="Produce a JSON consumable grouping of servers for Ansible")
|
||||
parser.add_option('--host', default=None, dest="host",
|
||||
help="Generate additional host specific details for given host for Ansible")
|
||||
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('-p', default=False, dest="prefix_org_name", action="store_true",
|
||||
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')
|
||||
|
||||
|
||||
# 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']
|
||||
|
||||
except (OSError) as e:
|
||||
print('Problem executing the command "%s system-groups": %s' %
|
||||
(SW_REPORT, str(e)), file=sys.stderr)
|
||||
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() )
|
||||
|
||||
except (OSError) as e:
|
||||
print('Problem executing the command "%s inventory": %s' %
|
||||
(SW_REPORT, str(e)), file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
groups = {}
|
||||
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'] ]
|
||||
|
||||
# shall we add the org_id as prefix to the group name:
|
||||
if options.prefix_org_name:
|
||||
prefix = org_id + "-"
|
||||
group_name = prefix + system['spacewalk_group_name']
|
||||
else:
|
||||
group_name = system['spacewalk_group_name']
|
||||
|
||||
# if we are limited to one organization:
|
||||
if options.org_number:
|
||||
if org_id == options.org_number:
|
||||
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'] ]
|
||||
# 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'] ]
|
||||
|
||||
except (OSError) as e:
|
||||
print('Problem executing the command "%s system-groups-systems": %s' %
|
||||
(SW_REPORT, str(e)), file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
if options.human:
|
||||
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["_meta"] = meta
|
||||
print(json.dumps( final ))
|
||||
#print(json.dumps(groups))
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
# Return a details information concerning the spacewalk server
|
||||
#------------------------------
|
||||
elif options.host:
|
||||
|
||||
host_details = {}
|
||||
try:
|
||||
for system in spacewalk_report('inventory'):
|
||||
if system['spacewalk_hostname'] == options.host:
|
||||
host_details = system
|
||||
break
|
||||
|
||||
except (OSError) as e:
|
||||
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() ) ) )
|
||||
sys.exit(0)
|
||||
|
||||
else:
|
||||
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
@ -1,118 +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 sys
|
||||
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)
|
||||
|
||||
VBOX="VBoxManage"
|
||||
|
||||
|
||||
def get_hosts(host=None):
|
||||
|
||||
returned = {}
|
||||
try:
|
||||
if host:
|
||||
p = Popen([VBOX, 'showvminfo', host], stdout=PIPE)
|
||||
else:
|
||||
returned = { 'all': set(), '_metadata': {} }
|
||||
p = Popen([VBOX, 'list', '-l', 'vms'], stdout=PIPE)
|
||||
except:
|
||||
sys.exit(1)
|
||||
|
||||
hostvars = {}
|
||||
prevkey = pref_k = ''
|
||||
|
||||
for line in p.stdout.readlines():
|
||||
|
||||
try:
|
||||
k,v = line.split(':',1)
|
||||
except:
|
||||
continue
|
||||
|
||||
if k == '':
|
||||
continue
|
||||
|
||||
v = v.strip()
|
||||
if k.startswith('Name'):
|
||||
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)
|
||||
ipinfo = x.stdout.read()
|
||||
if 'Value' in ipinfo:
|
||||
a,ip = ipinfo.split(':',1)
|
||||
hostvars[curname]['ansible_ssh_host'] = ip.strip()
|
||||
except:
|
||||
pass
|
||||
|
||||
continue
|
||||
|
||||
if not host:
|
||||
if k == 'Groups':
|
||||
for group in v.split('/'):
|
||||
if group:
|
||||
if group not in returned:
|
||||
returned[group] = set()
|
||||
returned[group].add(curname)
|
||||
returned['all'].add(curname)
|
||||
continue
|
||||
|
||||
pref_k = 'vbox_' + k.strip().replace(' ','_')
|
||||
if k.startswith(' '):
|
||||
if prevkey not in hostvars[curname]:
|
||||
hostvars[curname][prevkey] = {}
|
||||
hostvars[curname][prevkey][pref_k]= v
|
||||
else:
|
||||
if v != '':
|
||||
hostvars[curname][pref_k] = v
|
||||
|
||||
prevkey = pref_k
|
||||
|
||||
if not host:
|
||||
returned['_metadata']['hostvars'] = hostvars
|
||||
else:
|
||||
returned = hostvars[host]
|
||||
return returned
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
inventory = {}
|
||||
hostname = None
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
if sys.argv[1] == "--host":
|
||||
hostname = sys.argv[2]
|
||||
|
||||
if hostname:
|
||||
inventory = get_hosts(hostname)
|
||||
else:
|
||||
inventory = get_hosts()
|
||||
|
||||
sys.stdout.write(json.dumps(inventory, indent=2, cls=SetEncoder))
|
||||
@ -1,50 +0,0 @@
|
||||
# Ansible VMware external inventory script settings
|
||||
|
||||
[defaults]
|
||||
|
||||
# If true (the default), return only guest VMs. If false, also return host
|
||||
# systems in the results.
|
||||
guests_only = True
|
||||
|
||||
# Specify an alternate group name for guest VMs. If not defined, defaults to
|
||||
# the basename of the inventory script + "_vm", e.g. "vmware_vm".
|
||||
#vm_group = vm_group_name
|
||||
|
||||
# Specify an alternate group name for host systems when guests_only=false.
|
||||
# If not defined, defaults to the basename of the inventory script + "_hw",
|
||||
# e.g. "vmware_hw".
|
||||
#hw_group = hw_group_name
|
||||
|
||||
# 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_dir = ~/.cache/ansible
|
||||
|
||||
# Specify a prefix filter. Any VMs with names beginning with this string will
|
||||
# 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
|
||||
# included with the hostname, e.g.: vcenter.example.com:8443. This setting
|
||||
# may also be defined via the VMWARE_HOST environment variable.
|
||||
host = vcenter.example.com
|
||||
|
||||
# Specify a username to access the vCenter host. This setting may also be
|
||||
# defined with the VMWARE_USER environment variable.
|
||||
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
|
||||
@ -1,470 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
VMware Inventory Script
|
||||
=======================
|
||||
|
||||
Retrieve information about virtual machines from a vCenter server or
|
||||
standalone ESX host. When `group_by=false` (in the INI file), host systems
|
||||
are also returned in addition to VMs.
|
||||
|
||||
This script will attempt to read configuration from an INI file with the same
|
||||
base filename if present, or `vmware.ini` if not. It is possible to create
|
||||
symlinks to the inventory script to support multiple configurations, e.g.:
|
||||
|
||||
* `vmware.py` (this script)
|
||||
* `vmware.ini` (default configuration, will be read by `vmware.py`)
|
||||
* `vmware_test.py` (symlink to `vmware.py`)
|
||||
* `vmware_test.ini` (test configuration, will be read by `vmware_test.py`)
|
||||
* `vmware_other.py` (symlink to `vmware.py`, will read `vmware.ini` since no
|
||||
`vmware_other.ini` exists)
|
||||
|
||||
The path to an INI file may also be specified via the `VMWARE_INI` environment
|
||||
variable, in which case the filename matching rules above will not apply.
|
||||
|
||||
Host and authentication parameters may be specified via the `VMWARE_HOST`,
|
||||
`VMWARE_USER` and `VMWARE_PASSWORD` environment variables; these options will
|
||||
take precedence over options present in the INI file. An INI file is not
|
||||
required if these options are specified using environment variables.
|
||||
'''
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import collections
|
||||
import json
|
||||
import logging
|
||||
import optparse
|
||||
import os
|
||||
import ssl
|
||||
import sys
|
||||
import time
|
||||
import ConfigParser
|
||||
|
||||
from six import text_type, string_types
|
||||
|
||||
# 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 suds.sudsobject import Object as SudsObject
|
||||
|
||||
|
||||
class VMwareInventory(object):
|
||||
|
||||
def __init__(self, guests_only=None):
|
||||
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']
|
||||
for config_file in config_files:
|
||||
if os.path.exists(config_file):
|
||||
self.config.read(config_file)
|
||||
break
|
||||
|
||||
# Retrieve only guest VMs, or include host systems?
|
||||
if guests_only is not None:
|
||||
self.guests_only = guests_only
|
||||
elif self.config.has_option('defaults', 'guests_only'):
|
||||
self.guests_only = self.config.getboolean('defaults', 'guests_only')
|
||||
else:
|
||||
self.guests_only = True
|
||||
|
||||
# Read authentication information from VMware environment variables
|
||||
# (if set), otherwise from INI file.
|
||||
auth_host = os.environ.get('VMWARE_HOST')
|
||||
if not auth_host and self.config.has_option('auth', 'host'):
|
||||
auth_host = self.config.get('auth', 'host')
|
||||
auth_user = os.environ.get('VMWARE_USER')
|
||||
if not auth_user and self.config.has_option('auth', 'user'):
|
||||
auth_user = self.config.get('auth', 'user')
|
||||
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)
|
||||
|
||||
def _put_cache(self, name, value):
|
||||
'''
|
||||
Saves the value to cache with the name given.
|
||||
'''
|
||||
if self.config.has_option('defaults', 'cache_dir'):
|
||||
cache_dir = os.path.expanduser(self.config.get('defaults', 'cache_dir'))
|
||||
if not os.path.exists(cache_dir):
|
||||
os.makedirs(cache_dir)
|
||||
cache_file = os.path.join(cache_dir, name)
|
||||
with open(cache_file, 'w') as cache:
|
||||
json.dump(value, cache)
|
||||
|
||||
def _get_cache(self, name, default=None):
|
||||
'''
|
||||
Retrieves the value from cache for the given name.
|
||||
'''
|
||||
if self.config.has_option('defaults', 'cache_dir'):
|
||||
cache_dir = self.config.get('defaults', 'cache_dir')
|
||||
cache_file = os.path.join(cache_dir, name)
|
||||
if os.path.exists(cache_file):
|
||||
if self.config.has_option('defaults', 'cache_max_age'):
|
||||
cache_max_age = self.config.getint('defaults', 'cache_max_age')
|
||||
else:
|
||||
cache_max_age = 0
|
||||
cache_stat = os.stat(cache_file)
|
||||
if (cache_stat.st_mtime + cache_max_age) >= time.time():
|
||||
with open(cache_file) as cache:
|
||||
return json.load(cache)
|
||||
return default
|
||||
|
||||
def _flatten_dict(self, d, parent_key='', sep='_'):
|
||||
'''
|
||||
Flatten nested dicts by combining keys with a separator. Lists with
|
||||
only string items are included as is; any other lists are discarded.
|
||||
'''
|
||||
items = []
|
||||
for k, v in d.items():
|
||||
if k.startswith('_'):
|
||||
continue
|
||||
new_key = parent_key + sep + k if parent_key else k
|
||||
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]):
|
||||
items.append((new_key, v))
|
||||
else:
|
||||
items.append((new_key, v))
|
||||
return dict(items)
|
||||
|
||||
def _get_obj_info(self, obj, depth=99, seen=None):
|
||||
'''
|
||||
Recursively build a data structure for the given pSphere object (depth
|
||||
only applies to ManagedObject instances).
|
||||
'''
|
||||
seen = seen or set()
|
||||
if isinstance(obj, ManagedObject):
|
||||
try:
|
||||
obj_unicode = text_type(getattr(obj, 'name'))
|
||||
except AttributeError:
|
||||
obj_unicode = ()
|
||||
if obj in seen:
|
||||
return obj_unicode
|
||||
seen.add(obj)
|
||||
if depth <= 0:
|
||||
return obj_unicode
|
||||
d = {}
|
||||
for attr in dir(obj):
|
||||
if attr.startswith('_'):
|
||||
continue
|
||||
try:
|
||||
val = getattr(obj, attr)
|
||||
obj_info = self._get_obj_info(val, depth - 1, seen)
|
||||
if obj_info != ():
|
||||
d[attr] = obj_info
|
||||
except Exception as e:
|
||||
pass
|
||||
return d
|
||||
elif isinstance(obj, SudsObject):
|
||||
d = {}
|
||||
for key, val in iter(obj):
|
||||
obj_info = self._get_obj_info(val, depth, seen)
|
||||
if obj_info != ():
|
||||
d[key] = obj_info
|
||||
return d
|
||||
elif isinstance(obj, (list, tuple)):
|
||||
l = []
|
||||
for val in iter(obj):
|
||||
obj_info = self._get_obj_info(val, depth, seen)
|
||||
if obj_info != ():
|
||||
l.append(obj_info)
|
||||
return l
|
||||
elif isinstance(obj, (type(None), bool, int, long, float, string_types)):
|
||||
return obj
|
||||
else:
|
||||
return ()
|
||||
|
||||
def _get_host_info(self, host, prefix='vmware'):
|
||||
'''
|
||||
Return a flattened dict with info about the given host system.
|
||||
'''
|
||||
host_info = {
|
||||
'name': host.name,
|
||||
}
|
||||
for attr in ('datastore', 'network', 'vm'):
|
||||
try:
|
||||
value = getattr(host, attr)
|
||||
host_info['%ss' % attr] = self._get_obj_info(value, depth=0)
|
||||
except AttributeError:
|
||||
host_info['%ss' % attr] = []
|
||||
for k, v in self._get_obj_info(host.summary, depth=0).items():
|
||||
if isinstance(v, collections.MutableMapping):
|
||||
for k2, v2 in v.items():
|
||||
host_info[k2] = v2
|
||||
elif k != 'host':
|
||||
host_info[k] = v
|
||||
try:
|
||||
host_info['ipAddress'] = host.config.network.vnic[0].spec.ip.ipAddress
|
||||
except Exception as e:
|
||||
print(e, file=sys.stderr)
|
||||
host_info = self._flatten_dict(host_info, prefix)
|
||||
if ('%s_ipAddress' % prefix) in host_info:
|
||||
host_info['ansible_ssh_host'] = host_info['%s_ipAddress' % prefix]
|
||||
return host_info
|
||||
|
||||
def _get_vm_info(self, vm, prefix='vmware'):
|
||||
'''
|
||||
Return a flattened dict with info about the given virtual machine.
|
||||
'''
|
||||
vm_info = {
|
||||
'name': vm.name,
|
||||
}
|
||||
for attr in ('datastore', 'network'):
|
||||
try:
|
||||
value = getattr(vm, attr)
|
||||
vm_info['%ss' % attr] = self._get_obj_info(value, depth=0)
|
||||
except AttributeError:
|
||||
vm_info['%ss' % attr] = []
|
||||
try:
|
||||
vm_info['resourcePool'] = self._get_obj_info(vm.resourcePool, depth=0)
|
||||
except AttributeError:
|
||||
vm_info['resourcePool'] = ''
|
||||
try:
|
||||
vm_info['guestState'] = vm.guest.guestState
|
||||
except AttributeError:
|
||||
vm_info['guestState'] = ''
|
||||
for k, v in self._get_obj_info(vm.summary, depth=0).items():
|
||||
if isinstance(v, collections.MutableMapping):
|
||||
for k2, v2 in v.items():
|
||||
if k2 == 'host':
|
||||
k2 = 'hostSystem'
|
||||
vm_info[k2] = v2
|
||||
elif k != 'vm':
|
||||
vm_info[k] = v
|
||||
vm_info = self._flatten_dict(vm_info, prefix)
|
||||
if ('%s_ipAddress' % prefix) in vm_info:
|
||||
vm_info['ansible_ssh_host'] = vm_info['%s_ipAddress' % prefix]
|
||||
return vm_info
|
||||
|
||||
def _add_host(self, inv, parent_group, host_name):
|
||||
'''
|
||||
Add the host to the parent group in the given inventory.
|
||||
'''
|
||||
p_group = inv.setdefault(parent_group, [])
|
||||
if isinstance(p_group, dict):
|
||||
group_hosts = p_group.setdefault('hosts', [])
|
||||
else:
|
||||
group_hosts = p_group
|
||||
if host_name not in group_hosts:
|
||||
group_hosts.append(host_name)
|
||||
|
||||
def _add_child(self, inv, parent_group, child_group):
|
||||
'''
|
||||
Add a child group to a parent group in the given inventory.
|
||||
'''
|
||||
if parent_group != 'all':
|
||||
p_group = inv.setdefault(parent_group, {})
|
||||
if not isinstance(p_group, dict):
|
||||
inv[parent_group] = {'hosts': p_group}
|
||||
p_group = inv[parent_group]
|
||||
group_children = p_group.setdefault('children', [])
|
||||
if child_group not in group_children:
|
||||
group_children.append(child_group)
|
||||
inv.setdefault(child_group, [])
|
||||
|
||||
def get_inventory(self, meta_hostvars=True):
|
||||
'''
|
||||
Reads the inventory from cache or VMware API via pSphere.
|
||||
'''
|
||||
# Use different cache names for guests only vs. all hosts.
|
||||
if self.guests_only:
|
||||
cache_name = '__inventory_guests__'
|
||||
else:
|
||||
cache_name = '__inventory_all__'
|
||||
|
||||
inv = self._get_cache(cache_name, None)
|
||||
if inv is not None:
|
||||
return inv
|
||||
|
||||
inv = {'all': {'hosts': []}}
|
||||
if meta_hostvars:
|
||||
inv['_meta'] = {'hostvars': {}}
|
||||
|
||||
default_group = os.path.basename(sys.argv[0]).rstrip('.py')
|
||||
|
||||
if not self.guests_only:
|
||||
if self.config.has_option('defaults', 'hw_group'):
|
||||
hw_group = self.config.get('defaults', 'hw_group')
|
||||
else:
|
||||
hw_group = default_group + '_hw'
|
||||
|
||||
if self.config.has_option('defaults', 'vm_group'):
|
||||
vm_group = self.config.get('defaults', 'vm_group')
|
||||
else:
|
||||
vm_group = default_group + '_vm'
|
||||
|
||||
if self.config.has_option('defaults', 'prefix_filter'):
|
||||
prefix_filter = self.config.get('defaults', 'prefix_filter')
|
||||
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:
|
||||
|
||||
if not self.guests_only:
|
||||
self._add_host(inv, 'all', host.name)
|
||||
self._add_host(inv, hw_group, host.name)
|
||||
host_info = self._get_host_info(host)
|
||||
if meta_hostvars:
|
||||
inv['_meta']['hostvars'][host.name] = host_info
|
||||
self._put_cache(host.name, host_info)
|
||||
|
||||
# Loop through all VMs on physical host.
|
||||
for vm in host.vm:
|
||||
if prefix_filter:
|
||||
if vm.name.startswith( prefix_filter ):
|
||||
continue
|
||||
self._add_host(inv, 'all', vm.name)
|
||||
self._add_host(inv, vm_group, vm.name)
|
||||
vm_info = self._get_vm_info(vm)
|
||||
if meta_hostvars:
|
||||
inv['_meta']['hostvars'][vm.name] = vm_info
|
||||
self._put_cache(vm.name, vm_info)
|
||||
|
||||
# Group by resource pool.
|
||||
vm_resourcePool = vm_info.get('vmware_resourcePool', None)
|
||||
if vm_resourcePool:
|
||||
self._add_child(inv, vm_group, 'resource_pools')
|
||||
self._add_child(inv, 'resource_pools', vm_resourcePool)
|
||||
self._add_host(inv, vm_resourcePool, vm.name)
|
||||
|
||||
# Group by datastore.
|
||||
for vm_datastore in vm_info.get('vmware_datastores', []):
|
||||
self._add_child(inv, vm_group, 'datastores')
|
||||
self._add_child(inv, 'datastores', vm_datastore)
|
||||
self._add_host(inv, vm_datastore, vm.name)
|
||||
|
||||
# Group by network.
|
||||
for vm_network in vm_info.get('vmware_networks', []):
|
||||
self._add_child(inv, vm_group, 'networks')
|
||||
self._add_child(inv, 'networks', vm_network)
|
||||
self._add_host(inv, vm_network, vm.name)
|
||||
|
||||
# Group by guest OS.
|
||||
vm_guestId = vm_info.get('vmware_guestId', None)
|
||||
if vm_guestId:
|
||||
self._add_child(inv, vm_group, 'guests')
|
||||
self._add_child(inv, 'guests', vm_guestId)
|
||||
self._add_host(inv, vm_guestId, vm.name)
|
||||
|
||||
# Group all VM templates.
|
||||
vm_template = vm_info.get('vmware_template', False)
|
||||
if vm_template:
|
||||
self._add_child(inv, vm_group, 'templates')
|
||||
self._add_host(inv, 'templates', vm.name)
|
||||
|
||||
self._put_cache(cache_name, inv)
|
||||
return inv
|
||||
|
||||
def get_host(self, hostname):
|
||||
'''
|
||||
Read info about a specific host or VM from cache or VMware API.
|
||||
'''
|
||||
inv = self._get_cache(hostname, None)
|
||||
if inv is not None:
|
||||
return inv
|
||||
|
||||
if not self.guests_only:
|
||||
try:
|
||||
host = HostSystem.get(self.client, name=hostname)
|
||||
inv = self._get_host_info(host)
|
||||
except ObjectNotFoundError:
|
||||
pass
|
||||
|
||||
if inv is None:
|
||||
try:
|
||||
vm = VirtualMachine.get(self.client, name=hostname)
|
||||
inv = self._get_vm_info(vm)
|
||||
except ObjectNotFoundError:
|
||||
pass
|
||||
|
||||
if inv is not None:
|
||||
self._put_cache(hostname, inv)
|
||||
return inv or {}
|
||||
|
||||
|
||||
def main():
|
||||
parser = optparse.OptionParser()
|
||||
parser.add_option('--list', action='store_true', dest='list',
|
||||
default=False, help='Output inventory groups and hosts')
|
||||
parser.add_option('--host', dest='host', default=None, metavar='HOST',
|
||||
help='Output variables only for the given hostname')
|
||||
# Additional options for use when running the script standalone, but never
|
||||
# used by Ansible.
|
||||
parser.add_option('--pretty', action='store_true', dest='pretty',
|
||||
default=False, help='Output nicely-formatted JSON')
|
||||
parser.add_option('--include-host-systems', action='store_true',
|
||||
dest='include_host_systems', default=False,
|
||||
help='Include host systems in addition to VMs')
|
||||
parser.add_option('--no-meta-hostvars', action='store_false',
|
||||
dest='meta_hostvars', default=True,
|
||||
help='Exclude [\'_meta\'][\'hostvars\'] with --list')
|
||||
options, args = parser.parse_args()
|
||||
|
||||
if options.include_host_systems:
|
||||
vmware_inventory = VMwareInventory(guests_only=False)
|
||||
else:
|
||||
vmware_inventory = VMwareInventory()
|
||||
if options.host is not None:
|
||||
inventory = vmware_inventory.get_host(options.host)
|
||||
else:
|
||||
inventory = vmware_inventory.get_inventory(options.meta_hostvars)
|
||||
|
||||
json_kwargs = {}
|
||||
if options.pretty:
|
||||
json_kwargs.update({'indent': 4, 'sort_keys': True})
|
||||
json.dump(inventory, sys.stdout, **json_kwargs)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -1,71 +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
|
||||
username=administrator@vsphere.local
|
||||
|
||||
# The password for the vsphere API
|
||||
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
|
||||
|
||||
|
||||
# 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'}}
|
||||
@ -1,553 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Requirements
|
||||
# - pyvmomi >= 6.0.0.2016.4
|
||||
|
||||
# TODO:
|
||||
# * more jq examples
|
||||
# * optional folder heirarchy
|
||||
|
||||
"""
|
||||
$ 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 argparse
|
||||
import atexit
|
||||
import datetime
|
||||
import getpass
|
||||
import jinja2
|
||||
import os
|
||||
import six
|
||||
import ssl
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
from collections import defaultdict
|
||||
from six.moves import configparser
|
||||
from time import time
|
||||
|
||||
HAS_PYVMOMI = False
|
||||
try:
|
||||
from pyVmomi import vim
|
||||
from pyVim.connect import SmartConnect, Disconnect
|
||||
HAS_PYVMOMI = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
hasvcr = False
|
||||
try:
|
||||
import vcr
|
||||
hasvcr = True
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
class VMWareInventory(object):
|
||||
|
||||
__name__ = 'VMWareInventory'
|
||||
|
||||
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
|
||||
server = None
|
||||
port = None
|
||||
username = None
|
||||
password = None
|
||||
host_filters = []
|
||||
groupby_patterns = []
|
||||
|
||||
bad_types = ['Array', 'disabledMethod', 'declaredAlarmState']
|
||||
if (sys.version_info > (3, 0)):
|
||||
safe_types = [int, bool, str, float, None]
|
||||
else:
|
||||
safe_types = [int, long, bool, str, float, None]
|
||||
iter_types = [dict, list]
|
||||
skip_keys = ['dynamicproperty', 'dynamictype', 'managedby', 'childtype']
|
||||
|
||||
|
||||
def _empty_inventory(self):
|
||||
return {"_meta" : {"hostvars" : {}}}
|
||||
|
||||
|
||||
def __init__(self, load=True):
|
||||
self.inventory = self._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(text)
|
||||
|
||||
def show(self):
|
||||
# Data to print
|
||||
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 '''
|
||||
|
||||
instances = self.get_instances()
|
||||
self.instances = instances
|
||||
self.inventory = self.instances_to_inventory(instances)
|
||||
self.write_to_cache(self.inventory, self.cache_path_cache)
|
||||
|
||||
|
||||
def write_to_cache(self, data, cache_path):
|
||||
|
||||
''' 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,
|
||||
'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 }
|
||||
}
|
||||
|
||||
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.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.port = int(os.environ.get('VMWARE_PORT', config.get('vmware', 'port')))
|
||||
self.username = os.environ.get('VMWARE_USERNAME', config.get('vmware', '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
|
||||
else:
|
||||
self.validate_certs = True
|
||||
|
||||
# behavior control
|
||||
self.maxlevel = int(config.get('vmware', 'max_object_level'))
|
||||
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.host_filters = list(config.get('vmware', 'host_filters').split(','))
|
||||
self.groupby_patterns = list(config.get('vmware', 'groupby_patterns').split(','))
|
||||
|
||||
# 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 '''
|
||||
|
||||
instances = []
|
||||
|
||||
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
|
||||
|
||||
instances = self._get_instances(kwargs)
|
||||
return instances
|
||||
|
||||
|
||||
def _get_instances(self, inkwargs):
|
||||
|
||||
''' Make API calls '''
|
||||
|
||||
instances = []
|
||||
si = SmartConnect(**inkwargs)
|
||||
|
||||
self.debugl('# retrieving instances')
|
||||
if not si:
|
||||
print("Could not connect to the specified host using specified "
|
||||
"username and password")
|
||||
return -1
|
||||
atexit.register(Disconnect, si)
|
||||
content = si.RetrieveContent()
|
||||
|
||||
# Create a search container 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("# total instances retrieved %s" % len(instances))
|
||||
|
||||
instance_tuples = []
|
||||
for instance in sorted(instances):
|
||||
ifacts = self.facts_from_vobj(instance)
|
||||
instance_tuples.append((instance, ifacts))
|
||||
return instance_tuples
|
||||
|
||||
|
||||
def instances_to_inventory(self, instances):
|
||||
|
||||
''' Convert a list of vm objects into a json compliant inventory '''
|
||||
|
||||
inventory = self._empty_inventory()
|
||||
inventory['all'] = {}
|
||||
inventory['all']['hosts'] = []
|
||||
last_idata = None
|
||||
total = len(instances)
|
||||
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 not k 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 as e:
|
||||
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)
|
||||
|
||||
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 = jinja2.Template(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_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: %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 not x in self.bad_types]
|
||||
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)
|
||||
|
||||
return rdata
|
||||
|
||||
|
||||
def _process_object_types(self, vobj, level=0):
|
||||
''' Serialize an object '''
|
||||
rdata = {}
|
||||
|
||||
if vobj is None:
|
||||
rdata = None
|
||||
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), int) or isinstance(vobj, int):
|
||||
rdata = vobj
|
||||
elif issubclass(type(vobj), float) or isinstance(vobj, float):
|
||||
rdata = vobj
|
||||
elif issubclass(type(vobj), long) or isinstance(vobj, long):
|
||||
rdata = vobj
|
||||
elif issubclass(type(vobj), list) or issubclass(type(vobj), tuple):
|
||||
rdata = []
|
||||
try:
|
||||
vobj = sorted(vobj)
|
||||
except Exception as e:
|
||||
pass
|
||||
for vi in vobj:
|
||||
if (level+1 <= self.maxlevel):
|
||||
#vid = self.facts_from_vobj(vi, level=(level+1))
|
||||
vid = self._process_object_types(vi, 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 not x in self.bad_types]
|
||||
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):
|
||||
rdata[method] = self._process_object_types(methodToCall, level=(level+1))
|
||||
else:
|
||||
pass
|
||||
|
||||
if not rdata:
|
||||
rdata = None
|
||||
return rdata
|
||||
|
||||
def get_host_info(self, host):
|
||||
|
||||
''' Return hostvars for a single host '''
|
||||
|
||||
return self.inventory['_meta']['hostvars'][host]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run the script
|
||||
print(VMWareInventory().show())
|
||||
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# (c) 2015, Dagobert Michelsen <dam@baltic-online.de>
|
||||
#
|
||||
# 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 subprocess import Popen,PIPE
|
||||
import sys
|
||||
import json
|
||||
|
||||
result = {}
|
||||
result['all'] = {}
|
||||
|
||||
pipe = Popen(['zoneadm', 'list', '-ip'], stdout=PIPE, universal_newlines=True)
|
||||
result['all']['hosts'] = []
|
||||
for l in pipe.stdout.readlines():
|
||||
# 1:work:running:/zones/work:3126dc59-9a07-4829-cde9-a816e4c5040e:native:shared
|
||||
s = l.split(':')
|
||||
if s[1] != 'global':
|
||||
result['all']['hosts'].append(s[1])
|
||||
|
||||
result['all']['vars'] = {}
|
||||
result['all']['vars']['ansible_connection'] = 'zone'
|
||||
|
||||
if len(sys.argv) == 2 and sys.argv[1] == '--list':
|
||||
print(json.dumps(result))
|
||||
elif len(sys.argv) == 3 and sys.argv[1] == '--host':
|
||||
print(json.dumps({'ansible_connection': 'zone'}))
|
||||
else:
|
||||
sys.stderr.write("Need an argument, either --list or --host <host>\n")
|
||||
@ -1,78 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# (c) 2014, Matt Martz <matt@sivel.net>
|
||||
#
|
||||
# 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/>.
|
||||
#
|
||||
#
|
||||
# Script to be used with vault_password_file or --vault-password-file
|
||||
# to retrieve the vault password via your OSes native keyring application
|
||||
#
|
||||
# This script requires the ``keyring`` python module
|
||||
#
|
||||
# Add a [vault] section to your ansible.cfg file,
|
||||
# the only option is 'username'. Example:
|
||||
#
|
||||
# [vault]
|
||||
# username = 'ansible_vault'
|
||||
#
|
||||
# Additionally, it would be a good idea to configure vault_password_file in
|
||||
# ansible.cfg
|
||||
#
|
||||
# [defaults]
|
||||
# ...
|
||||
# vault_password_file = /path/to/vault-keyring.py
|
||||
# ...
|
||||
#
|
||||
# To set your password: python /path/to/vault-keyring.py set
|
||||
#
|
||||
# If you choose to not configure the path to vault_password_file in ansible.cfg
|
||||
# your ansible-playbook command may look like:
|
||||
#
|
||||
# ansible-playbook --vault-password-file=/path/to/vault-keyring.py site.yml
|
||||
|
||||
import sys
|
||||
import getpass
|
||||
import keyring
|
||||
import ConfigParser
|
||||
|
||||
|
||||
import ansible.constants as C
|
||||
|
||||
def main():
|
||||
(parser,config_path) = C.load_config_file()
|
||||
try:
|
||||
username = parser.get('vault', 'username')
|
||||
except ConfigParser.NoSectionError:
|
||||
sys.stderr.write('No [vault] section configured in config file: %s\n' % config_path)
|
||||
sys.exit(1)
|
||||
|
||||
if len(sys.argv) == 2 and sys.argv[1] == 'set':
|
||||
password = getpass.getpass()
|
||||
confirm = getpass.getpass('Confirm password: ')
|
||||
if password == confirm:
|
||||
keyring.set_password('ansible', username, password)
|
||||
else:
|
||||
sys.stderr.write('Passwords do not match\n')
|
||||
sys.exit(1)
|
||||
else:
|
||||
sys.stdout.write('%s\n' % keyring.get_password('ansible', username))
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -1,234 +0,0 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
LOGFILE = sphinx.log
|
||||
FULL_TRACEBACKS = -T
|
||||
CPUS ?= 4
|
||||
VERBOSITY ?= -v
|
||||
FORCE_REBUILD = -a -E
|
||||
NITPICK ?=
|
||||
SPHINXOPTS = -j $(CPUS) -w $(LOGFILE) $(FULL_TRACEBACKS) $(FORCE_REBUILD) $(NITPICK) $(VERBOSITY)
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
RSTDIR = rst
|
||||
MODULES_PATH = ../lib
|
||||
EXCLUDE_PATHS = ../lib/ansible/modules ../lib/ansible/utils/module_docs_fragments ../lib/ansible/compat/six ../lib/ansible/module_utils/six.py
|
||||
DOC_PROJECTS = "Ansible API"
|
||||
|
||||
# User-friendly check for sphinx-build
|
||||
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
|
||||
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
|
||||
endif
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " applehelp to make an Apple Help Book"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " xml to make Docutils-native XML files"
|
||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
@echo " coverage to run coverage check of the documentation (if enabled)"
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
rm -rf $(RSTDIR)/*.rst
|
||||
|
||||
.PHONY: apidoc
|
||||
apidoc:
|
||||
sphinx-apidoc --module-first --doc-project $(DOC_PROJECT) --force --maxdepth 7 -o $(RSTDIR) $(MODULES_PATH) $(EXCLUDE_PATHS)
|
||||
|
||||
.PHONY: html
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
.PHONY: docs
|
||||
docs: clean apidoc html
|
||||
|
||||
.PHONY: dirhtml
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
.PHONY: singlehtml
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
.PHONY: pickle
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
.PHONY: json
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
.PHONY: htmlhelp
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
.PHONY: qthelp
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Ansible.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Ansible.qhc"
|
||||
|
||||
.PHONY: applehelp
|
||||
applehelp:
|
||||
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
|
||||
@echo
|
||||
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
|
||||
@echo "N.B. You won't be able to view it unless you put it in" \
|
||||
"~/Library/Documentation/Help or install it in your application" \
|
||||
"bundle."
|
||||
|
||||
.PHONY: devhelp
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/Ansible"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Ansible"
|
||||
@echo "# devhelp"
|
||||
|
||||
.PHONY: epub
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
.PHONY: latex
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
.PHONY: latexpdf
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
.PHONY: latexpdfja
|
||||
latexpdfja:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
.PHONY: text
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
.PHONY: man
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
.PHONY: texinfo
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
.PHONY: info
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
.PHONY: gettext
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
.PHONY: changes
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
.PHONY: linkcheck
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
.PHONY: doctest
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
||||
.PHONY: coverage
|
||||
coverage:
|
||||
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
|
||||
@echo "Testing of coverage in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/coverage/python.txt."
|
||||
|
||||
.PHONY: xml
|
||||
xml:
|
||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
||||
@echo
|
||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
||||
|
||||
.PHONY: pseudoxml
|
||||
pseudoxml:
|
||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||
@echo
|
||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
||||
309
docs-api/conf.py
309
docs-api/conf.py
@ -1,309 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Ansible documentation build configuration file, created by
|
||||
# sphinx-quickstart on Fri Jun 3 17:34:17 2016.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
#sys.path.insert(0, os.path.abspath('../bin'))
|
||||
sys.path.insert(0, os.path.abspath('../lib/ansible'))
|
||||
import sphinx_rtd_theme
|
||||
import alabaster
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.napoleon',
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.viewcode',
|
||||
'sphinx.ext.graphviz',
|
||||
'sphinx.ext.inheritance_diagram',
|
||||
'alabaster',
|
||||
]
|
||||
|
||||
#autodoc_default_flags = ['members', 'show-inheritance', 'inherited-members', 'undoc-members',]
|
||||
autodoc_default_flags = ['members', 'show-inheritance', 'undoc-members',]
|
||||
autoclass_content = 'both'
|
||||
autodoc_member_order = 'bysource'
|
||||
autodoc_mock_imports = ['xmltodict', 'winrm', 'redis', 'StricRedis']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Ansible'
|
||||
copyright = u'2016, Red Hat'
|
||||
author = u'Red Hat'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = u'2.2'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = u'1'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all
|
||||
# documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
||||
#keep_warnings = False
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = True
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
#html_theme = 'alabaster'
|
||||
#html_theme_path = ['../docsite/_themes']
|
||||
#html_theme = 'srtd'
|
||||
html_short_title = 'Ansible Documentation'
|
||||
|
||||
#html_theme = "sphinx_rtd_theme"
|
||||
#html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
|
||||
html_theme_path = [alabaster.get_path()]
|
||||
html_theme = 'alabaster'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (relative to this directory) to use as a favicon of
|
||||
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Add any extra paths that contain custom files (such as robots.txt or
|
||||
# .htaccess) here, relative to this directory. These files are copied
|
||||
# directly to the root of the documentation.
|
||||
#html_extra_path = []
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Language to be used for generating the HTML full-text search index.
|
||||
# Sphinx supports the following languages:
|
||||
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
|
||||
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
|
||||
#html_search_language = 'en'
|
||||
|
||||
# A dictionary with options for the search language support, empty by default.
|
||||
# Now only 'ja' uses this config value
|
||||
#html_search_options = {'type': 'default'}
|
||||
|
||||
# The name of a javascript file (relative to the configuration directory) that
|
||||
# implements a search results scorer. If empty, the default will be used.
|
||||
#html_search_scorer = 'scorer.js'
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Ansibledoc'
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'Ansible.tex', u'Ansible Documentation',
|
||||
u'Red Hat', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'ansible', u'Ansible Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'Ansible', u'Ansible Documentation',
|
||||
author, 'Ansible', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
#texinfo_no_detailmenu = False
|
||||
@ -1,8 +0,0 @@
|
||||
sphinx
|
||||
|
||||
# extensions
|
||||
sphinxcontrib-napoleon
|
||||
sphinxcontrib-inheritance
|
||||
|
||||
sphinx-rtd-theme
|
||||
alabaster
|
||||
@ -1,22 +0,0 @@
|
||||
.. ansible documentation master file, created by
|
||||
sphinx-quickstart on Tue Jun 14 10:43:24 2016.
|
||||
You can adapt this file completely to your liking, but it should at least
|
||||
contain the root `toctree` directive.
|
||||
|
||||
Welcome to ansible's documentation!
|
||||
===================================
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
Ansible api
|
||||
===========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 7
|
||||
|
||||
ansible
|
||||
72
docs/man/man1/ansible-doc.1
Normal file
72
docs/man/man1/ansible-doc.1
Normal file
@ -0,0 +1,72 @@
|
||||
'\" t
|
||||
.\" Title: ansible-doc
|
||||
.\" Author: :doctype:manpage
|
||||
.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
|
||||
.\" Date: 05/26/2014
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 1.7
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE\-DOC" "1" "05/26/2014" "Ansible 1\&.7" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.\" http://bugs.debian.org/507673
|
||||
.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * set default formatting
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" disable hyphenation
|
||||
.nh
|
||||
.\" disable justification (adjust text to left margin only)
|
||||
.ad l
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * MAIN CONTENT STARTS HERE *
|
||||
.\" -----------------------------------------------------------------
|
||||
.SH "NAME"
|
||||
ansible-doc \- show documentation on Ansible modules
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
ansible\-doc [\-M module_path] [\-l] [\-s] [module\&...]
|
||||
.SH "DESCRIPTION"
|
||||
.sp
|
||||
\fBansible\-doc\fR displays information on modules installed in Ansible libraries\&. It displays a terse listing of modules and their short descriptions, provides a printout of their DOCUMENTATION strings, and it can create a short "snippet" which can be pasted into a playbook\&.
|
||||
.SH "OPTIONS"
|
||||
.PP
|
||||
\fB\-M\fR \fIdirectory\fR, \fB\-\-module\-path=\fR\fIdirectory\fR
|
||||
.RS 4
|
||||
Add an additional directory to the default path for finding module libraries\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-s\fR, \fB\-\-snippet=\fR
|
||||
.RS 4
|
||||
Produce a snippet which can be copied into a playbook for modification, like a kind of task template\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-l\fR, \fB\-\-list=\fR
|
||||
.RS 4
|
||||
Produce a terse listing of modules and a short description of each\&.
|
||||
.RE
|
||||
.SH "AUTHOR"
|
||||
.sp
|
||||
ansible\-doc was originally written by Jan\-Piet Mens\&. See the AUTHORS file for a complete list of contributors\&.
|
||||
.SH "COPYRIGHT"
|
||||
.sp
|
||||
Copyright \(co 2012, Jan\-Piet Mens
|
||||
.sp
|
||||
Ansible is released under the terms of the GPLv3 License\&.
|
||||
.SH "SEE ALSO"
|
||||
.sp
|
||||
\fBansible\-playbook\fR(1), \fBansible\fR(1), \fBansible\-pull\fR(1)
|
||||
.sp
|
||||
Extensive documentation is available in the documentation site: http://docs\&.ansible\&.com\&. IRC and mailing list info can be found in file CONTRIBUTING\&.md, available in: https://github\&.com/ansible/ansible
|
||||
.SH "AUTHOR"
|
||||
.PP
|
||||
\fB:doctype:manpage\fR
|
||||
.RS 4
|
||||
Author.
|
||||
.RE
|
||||
@ -1,6 +1,6 @@
|
||||
ansible-doc(1)
|
||||
==============
|
||||
:doctype: manpage
|
||||
:doctype:manpage
|
||||
:man source: Ansible
|
||||
:man version: %VERSION%
|
||||
:man manual: System administration commands
|
||||
@ -28,11 +28,9 @@ playbook.
|
||||
OPTIONS
|
||||
-------
|
||||
|
||||
*-M* 'DIRECTORY', *--module-path=*'DIRECTORY'::
|
||||
*-M* 'directory', *--module-path=*'directory'::
|
||||
|
||||
the 'DIRECTORY' search path to load modules from. The default is
|
||||
'/usr/share/ansible'. This can also be set with the ANSIBLE_LIBRARY
|
||||
environment variable.
|
||||
Add an additional directory to the default path for finding module libraries.
|
||||
|
||||
*-s*, *--snippet=*::
|
||||
|
||||
@ -43,24 +41,6 @@ a kind of task template.
|
||||
|
||||
Produce a terse listing of modules and a short description of each.
|
||||
|
||||
|
||||
ENVIRONMENT
|
||||
-----------
|
||||
|
||||
ANSIBLE_LIBRARY -- Override the default ansible module library path
|
||||
|
||||
|
||||
FILES
|
||||
-----
|
||||
|
||||
/usr/share/ansible/ -- Default module library
|
||||
|
||||
/etc/ansible/ansible.cfg -- Config file, used if present
|
||||
|
||||
~/.ansible.cfg -- User config file, overrides the default config if present
|
||||
|
||||
|
||||
|
||||
AUTHOR
|
||||
------
|
||||
|
||||
@ -79,7 +59,7 @@ Ansible is released under the terms of the GPLv3 License.
|
||||
SEE ALSO
|
||||
--------
|
||||
|
||||
*ansible-playbook*(1), *ansible*(1), *ansible-pull*(1), *ansible-vault*(1), *ansible-galaxy*(1)
|
||||
*ansible-playbook*(1), *ansible*(1), *ansible-pull*(1)
|
||||
|
||||
Extensive documentation is available in the documentation site:
|
||||
<http://docs.ansible.com>. IRC and mailing list info can be found
|
||||
|
||||
180
docs/man/man1/ansible-galaxy.1
Normal file
180
docs/man/man1/ansible-galaxy.1
Normal file
@ -0,0 +1,180 @@
|
||||
'\" t
|
||||
.\" Title: ansible-galaxy
|
||||
.\" Author: [see the "AUTHOR" section]
|
||||
.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
|
||||
.\" Date: 05/26/2014
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 1.7
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE\-GALAXY" "1" "05/26/2014" "Ansible 1\&.7" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.\" http://bugs.debian.org/507673
|
||||
.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * set default formatting
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" disable hyphenation
|
||||
.nh
|
||||
.\" disable justification (adjust text to left margin only)
|
||||
.ad l
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * MAIN CONTENT STARTS HERE *
|
||||
.\" -----------------------------------------------------------------
|
||||
.SH "NAME"
|
||||
ansible-galaxy \- manage roles using galaxy\&.ansible\&.com
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
ansible\-galaxy [init|info|install|list|remove] [\-\-help] [options] \&...
|
||||
.SH "DESCRIPTION"
|
||||
.sp
|
||||
\fBAnsible Galaxy\fR is a shared repository for Ansible roles (added in ansible version 1\&.2)\&. The ansible\-galaxy command can be used to manage these roles, or by creating a skeleton framework for roles you\(cqd like to upload to Galaxy\&.
|
||||
.SH "COMMON OPTIONS"
|
||||
.PP
|
||||
\fB\-h\fR, \fB\-\-help\fR
|
||||
.RS 4
|
||||
Show a help message related to the given sub\-command\&.
|
||||
.RE
|
||||
.SH "INSTALL"
|
||||
.sp
|
||||
The \fBinstall\fR sub\-command is used to install roles\&.
|
||||
.SS "USAGE"
|
||||
.sp
|
||||
$ ansible\-galaxy install [options] [\-r FILE | role_name(s)[,version] | tar_file(s)]
|
||||
.sp
|
||||
Roles can be installed in several different ways:
|
||||
.sp
|
||||
.RS 4
|
||||
.ie n \{\
|
||||
\h'-04'\(bu\h'+03'\c
|
||||
.\}
|
||||
.el \{\
|
||||
.sp -1
|
||||
.IP \(bu 2.3
|
||||
.\}
|
||||
A username\&.rolename[,version] \- this will install a single role\&. The Galaxy API will be contacted to provide the information about the role, and the corresponding \&.tar\&.gz will be downloaded from
|
||||
\fBgithub\&.com\fR\&. If the version is omitted, the most recent version available will be installed\&.
|
||||
.RE
|
||||
.sp
|
||||
.RS 4
|
||||
.ie n \{\
|
||||
\h'-04'\(bu\h'+03'\c
|
||||
.\}
|
||||
.el \{\
|
||||
.sp -1
|
||||
.IP \(bu 2.3
|
||||
.\}
|
||||
A file name, using
|
||||
\fB\-r\fR
|
||||
\- this will install multiple roles listed one per line\&. The format of each line is the same as above: username\&.rolename[,version]
|
||||
.RE
|
||||
.sp
|
||||
.RS 4
|
||||
.ie n \{\
|
||||
\h'-04'\(bu\h'+03'\c
|
||||
.\}
|
||||
.el \{\
|
||||
.sp -1
|
||||
.IP \(bu 2.3
|
||||
.\}
|
||||
A \&.tar\&.gz of a valid role you\(cqve downloaded directly from
|
||||
\fBgithub\&.com\fR\&. This is mainly useful when the system running Ansible does not have access to the Galaxy API, for instance when behind a firewall or proxy\&.
|
||||
.RE
|
||||
.SS "OPTIONS"
|
||||
.PP
|
||||
\fB\-f\fR, \fB\-\-force\fR
|
||||
.RS 4
|
||||
Force overwriting an existing role\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-i\fR, \fB\-\-ignore\-errors\fR
|
||||
.RS 4
|
||||
Ignore errors and continue with the next specified role\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-n\fR, \fB\-\-no\-deps\fR
|
||||
.RS 4
|
||||
Don\(cqt download roles listed as dependencies\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-p\fR \fIROLES_PATH\fR, \fB\-\-roles\-path=\fR\fIROLES_PATH\fR
|
||||
.RS 4
|
||||
The path to the directory containing your roles\&. The default is the
|
||||
\fBroles_path\fR
|
||||
configured in your
|
||||
\fBansible\&.cfg\fR
|
||||
file (/etc/ansible/roles if not configured)
|
||||
.RE
|
||||
.PP
|
||||
\fB\-r\fR \fIROLE_FILE\fR, \fB\-\-role\-file=\fR\fIROLE_FILE\fR
|
||||
.RS 4
|
||||
A file containing a list of roles to be imported, as specified above\&. This option cannot be used if a rolename or \&.tar\&.gz have been specified\&.
|
||||
.RE
|
||||
.SH "REMOVE"
|
||||
.sp
|
||||
The \fBremove\fR sub\-command is used to remove one or more roles\&.
|
||||
.SS "USAGE"
|
||||
.sp
|
||||
$ ansible\-galaxy remove role1 role2 \&...
|
||||
.SS "OPTIONS"
|
||||
.PP
|
||||
\fB\-p\fR \fIROLES_PATH\fR, \fB\-\-roles\-path=\fR\fIROLES_PATH\fR
|
||||
.RS 4
|
||||
The path to the directory containing your roles\&. The default is the
|
||||
\fBroles_path\fR
|
||||
configured in your
|
||||
\fBansible\&.cfg\fR
|
||||
file (/etc/ansible/roles if not configured)
|
||||
.RE
|
||||
.SH "INIT"
|
||||
.sp
|
||||
The \fBinit\fR command is used to create an empty role suitable for uploading to https://galaxy\&.ansible\&.com (or for roles in general)\&.
|
||||
.SS "USAGE"
|
||||
.sp
|
||||
$ ansible\-galaxy init [options] role_name
|
||||
.SS "OPTIONS"
|
||||
.PP
|
||||
\fB\-f\fR, \fB\-\-force\fR
|
||||
.RS 4
|
||||
Force overwriting an existing role\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-p\fR \fIINIT_PATH\fR, \fB\-\-init\-path=\fR\fIINIT_PATH\fR
|
||||
.RS 4
|
||||
The path in which the skeleton role will be created\&.The default is the current working directory\&.
|
||||
.RE
|
||||
.SH "LIST"
|
||||
.sp
|
||||
The \fBlist\fR sub\-command is used to show what roles are currently instaled\&. You can specify a role name, and if installed only that role will be shown\&.
|
||||
.SS "USAGE"
|
||||
.sp
|
||||
$ ansible\-galaxy list [role_name]
|
||||
.SS "OPTIONS"
|
||||
.PP
|
||||
\fB\-p\fR \fIROLES_PATH\fR, \fB\-\-roles\-path=\fR\fIROLES_PATH\fR
|
||||
.RS 4
|
||||
The path to the directory containing your roles\&. The default is the
|
||||
\fBroles_path\fR
|
||||
configured in your
|
||||
\fBansible\&.cfg\fR
|
||||
file (/etc/ansible/roles if not configured)
|
||||
.RE
|
||||
.SH "AUTHOR"
|
||||
.sp
|
||||
Ansible was originally written by Michael DeHaan\&. See the AUTHORS file for a complete list of contributors\&.
|
||||
.SH "COPYRIGHT"
|
||||
.sp
|
||||
Copyright \(co 2014, Michael DeHaan
|
||||
.sp
|
||||
Ansible is released under the terms of the GPLv3 License\&.
|
||||
.SH "SEE ALSO"
|
||||
.sp
|
||||
\fBansible\fR(1), \fBansible\-pull\fR(1), \fBansible\-doc\fR(1)
|
||||
.sp
|
||||
Extensive documentation is available in the documentation site: http://docs\&.ansible\&.com\&. IRC and mailing list info can be found in file CONTRIBUTING\&.md, available in: https://github\&.com/ansible/ansible
|
||||
@ -12,15 +12,16 @@ ansible-galaxy - manage roles using galaxy.ansible.com
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
ansible-galaxy [delete|import|info|init|install|list|login|remove|search|setup] [--help] [options] ...
|
||||
ansible-galaxy [init|info|install|list|remove] [--help] [options] ...
|
||||
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
|
||||
*Ansible Galaxy* is a shared repository for Ansible roles.
|
||||
The ansible-galaxy command can be used to manage these roles,
|
||||
or for creating a skeleton framework for roles you'd like to upload to Galaxy.
|
||||
*Ansible Galaxy* is a shared repository for Ansible roles (added in
|
||||
ansible version 1.2). The ansible-galaxy command can be used to manage
|
||||
these roles, or by creating a skeleton framework for roles you'd like
|
||||
to upload to Galaxy.
|
||||
|
||||
COMMON OPTIONS
|
||||
--------------
|
||||
@ -29,6 +30,7 @@ COMMON OPTIONS
|
||||
|
||||
Show a help message related to the given sub-command.
|
||||
|
||||
|
||||
INSTALL
|
||||
-------
|
||||
|
||||
@ -41,16 +43,16 @@ $ ansible-galaxy install [options] [-r FILE | role_name(s)[,version] | tar_file(
|
||||
|
||||
Roles can be installed in several different ways:
|
||||
|
||||
* A username.rolename[,version] - this will install a single role. The Galaxy
|
||||
API will be contacted to provide the information about the role, and the
|
||||
corresponding .tar.gz will be downloaded from *github.com*. If the version
|
||||
* A username.rolename[,version] - this will install a single role. The Galaxy
|
||||
API will be contacted to provide the information about the role, and the
|
||||
corresponding .tar.gz will be downloaded from *github.com*. If the version
|
||||
is omitted, the most recent version available will be installed.
|
||||
|
||||
* A file name, using *-r* - this will install multiple roles listed one per
|
||||
line. The format of each line is the same as above: username.rolename[,version]
|
||||
|
||||
* A .tar.gz of a valid role you've downloaded directly from *github.com*. This
|
||||
is mainly useful when the system running Ansible does not have access to
|
||||
is mainly useful when the system running Ansible does not have access to
|
||||
the Galaxy API, for instance when behind a firewall or proxy.
|
||||
|
||||
|
||||
@ -120,14 +122,10 @@ Force overwriting an existing role.
|
||||
The path in which the skeleton role will be created.The default is the current
|
||||
working directory.
|
||||
|
||||
*--offline*::
|
||||
|
||||
Don't query the galaxy API when creating roles
|
||||
|
||||
LIST
|
||||
----
|
||||
|
||||
The *list* sub-command is used to show what roles are currently installed.
|
||||
The *list* sub-command is used to show what roles are currently instaled.
|
||||
You can specify a role name, and if installed only that role will be shown.
|
||||
|
||||
USAGE
|
||||
@ -144,204 +142,6 @@ The path to the directory containing your roles. The default is the *roles_path*
|
||||
configured in your *ansible.cfg* file (/etc/ansible/roles if not configured)
|
||||
|
||||
|
||||
SEARCH
|
||||
------
|
||||
|
||||
The *search* sub-command returns a filtered list of roles found on the remote
|
||||
server.
|
||||
|
||||
|
||||
USAGE
|
||||
~~~~~
|
||||
|
||||
$ ansible-galaxy search [options] [searchterm1 searchterm2]
|
||||
|
||||
|
||||
OPTIONS
|
||||
~~~~~~~
|
||||
*--galaxy-tags*::
|
||||
|
||||
Provide a comma separated list of Galaxy Tags on which to filter.
|
||||
|
||||
*--platforms*::
|
||||
|
||||
Provide a comma separated list of Platforms on which to filter.
|
||||
|
||||
*--author*::
|
||||
|
||||
Specify the username of a Galaxy contributor on which to filter.
|
||||
|
||||
*-c*, *--ignore-certs*::
|
||||
|
||||
Ignore TLS certificate errors.
|
||||
|
||||
*-s*, *--server*::
|
||||
|
||||
Override the default server https://galaxy.ansible.com.
|
||||
|
||||
|
||||
INFO
|
||||
----
|
||||
|
||||
The *info* sub-command shows detailed information for a specific role.
|
||||
Details returned about the role included information from the local copy
|
||||
as well as information from galaxy.ansible.com.
|
||||
|
||||
USAGE
|
||||
~~~~~
|
||||
|
||||
$ ansible-galaxy info [options] role_name[, version]
|
||||
|
||||
OPTIONS
|
||||
~~~~~~~
|
||||
|
||||
*-p* 'ROLES_PATH', *--roles-path=*'ROLES_PATH'::
|
||||
|
||||
The path to the directory containing your roles. The default is the *roles_path*
|
||||
configured in your *ansible.cfg* file (/etc/ansible/roles if not configured)
|
||||
|
||||
*-c*, *--ignore-certs*::
|
||||
|
||||
Ignore TLS certificate errors.
|
||||
|
||||
*-s*, *--server*::
|
||||
|
||||
Override the default server https://galaxy.ansible.com.
|
||||
|
||||
|
||||
LOGIN
|
||||
-----
|
||||
|
||||
The *login* sub-command is used to authenticate with galaxy.ansible.com.
|
||||
Authentication is required to use the import, delete and setup commands.
|
||||
It will authenticate the user, retrieve a token from Galaxy, and store it
|
||||
in the user's home directory.
|
||||
|
||||
USAGE
|
||||
~~~~~
|
||||
|
||||
$ ansible-galaxy login [options]
|
||||
|
||||
The *login* sub-command prompts for a *GitHub* username and password. It does
|
||||
NOT send your password to Galaxy. It actually authenticates with GitHub and
|
||||
creates a personal access token. It then sends the personal access token to
|
||||
Galaxy, which in turn verifies that you are you and returns a Galaxy access
|
||||
token. After authentication completes the *GitHub* personal access token is
|
||||
destroyed.
|
||||
|
||||
If you do not wish to use your GitHub password, or if you have two-factor
|
||||
authentication enabled with GitHub, use the *--github-token* option to pass a
|
||||
personal access token that you create. Log into GitHub, go to Settings and
|
||||
click on Personal Access Token to create a token.
|
||||
|
||||
OPTIONS
|
||||
~~~~~~~
|
||||
|
||||
*-c*, *--ignore-certs*::
|
||||
|
||||
Ignore TLS certificate errors.
|
||||
|
||||
*-s*, *--server*::
|
||||
|
||||
Override the default server https://galaxy.ansible.com.
|
||||
|
||||
*--github-token*::
|
||||
|
||||
Authenticate using a *GitHub* personal access token rather than a password.
|
||||
|
||||
|
||||
IMPORT
|
||||
------
|
||||
|
||||
Import a role from *GitHub* to galaxy.ansible.com. Requires the user first
|
||||
authenticate with galaxy.ansible.com using the *login* subcommand.
|
||||
|
||||
USAGE
|
||||
~~~~~
|
||||
|
||||
$ ansible-galaxy import [options] github_user github_repo
|
||||
|
||||
OPTIONS
|
||||
~~~~~~~
|
||||
*-c*, *--ignore-certs*::
|
||||
|
||||
Ignore TLS certificate errors.
|
||||
|
||||
*-s*, *--server*::
|
||||
|
||||
Override the default server https://galaxy.ansible.com.
|
||||
|
||||
*--branch*::
|
||||
|
||||
Provide a specific branch to import. When a branch is not specified the
|
||||
branch found in meta/main.yml is used. If no branch is specified in
|
||||
meta/main.yml, the repo's default branch (usually master) is used.
|
||||
|
||||
|
||||
DELETE
|
||||
------
|
||||
|
||||
The *delete* sub-command will delete a role from galaxy.ansible.com. Requires
|
||||
the user first authenticate with galaxy.ansible.com using the *login* subcommand.
|
||||
|
||||
USAGE
|
||||
~~~~~
|
||||
|
||||
$ ansible-galaxy delete [options] github_user github_repo
|
||||
|
||||
OPTIONS
|
||||
~~~~~~~
|
||||
|
||||
*-c*, *--ignore-certs*::
|
||||
|
||||
Ignore TLS certificate errors.
|
||||
|
||||
*-s*, *--server*::
|
||||
|
||||
Override the default server https://galaxy.ansible.com.
|
||||
|
||||
|
||||
SETUP
|
||||
-----
|
||||
|
||||
The *setup* sub-command creates an integration point for *Travis CI*, enabling
|
||||
galaxy.ansible.com to receive notifications from *Travis* on build completion.
|
||||
Requires the user first authenticate with galaxy.ansible.com using the *login*
|
||||
subcommand.
|
||||
|
||||
USAGE
|
||||
~~~~~
|
||||
|
||||
$ ansible-galaxy setup [options] source github_user github_repo secret
|
||||
|
||||
* Use *travis* as the source value. In the future additional source values may
|
||||
be added.
|
||||
|
||||
* Provide your *Travis* user token as the secret. The token is not stored by
|
||||
galaxy.ansible.com. A hash is created using github_user, github_repo
|
||||
and your token. The hash value is what actually gets stored.
|
||||
|
||||
OPTIONS
|
||||
~~~~~~~
|
||||
|
||||
*-c*, *--ignore-certs*::
|
||||
|
||||
Ignore TLS certificate errors.
|
||||
|
||||
*-s*, *--server*::
|
||||
|
||||
Override the default server https://galaxy.ansible.com.
|
||||
|
||||
--list::
|
||||
|
||||
Show your configured integrations. Provides the ID of each integration
|
||||
which can be used with the remove option.
|
||||
|
||||
--remove::
|
||||
|
||||
Remove a specific integration. Provide the ID of the integration to
|
||||
be removed.
|
||||
|
||||
AUTHOR
|
||||
------
|
||||
|
||||
@ -360,7 +160,7 @@ Ansible is released under the terms of the GPLv3 License.
|
||||
SEE ALSO
|
||||
--------
|
||||
|
||||
*ansible*(1), *ansible-pull*(1), *ansible-doc*(1), *ansible-playbook*(1), *ansible-vault*(1)
|
||||
*ansible*(1), *ansible-pull*(1), *ansible-doc*(1)
|
||||
|
||||
Extensive documentation is available in the documentation site:
|
||||
<http://docs.ansible.com>. IRC and mailing list info can be found
|
||||
|
||||
184
docs/man/man1/ansible-playbook.1
Normal file
184
docs/man/man1/ansible-playbook.1
Normal file
@ -0,0 +1,184 @@
|
||||
'\" t
|
||||
.\" Title: ansible-playbook
|
||||
.\" Author: :doctype:manpage
|
||||
.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
|
||||
.\" Date: 05/26/2014
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 1.7
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE\-PLAYBOOK" "1" "05/26/2014" "Ansible 1\&.7" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.\" http://bugs.debian.org/507673
|
||||
.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * set default formatting
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" disable hyphenation
|
||||
.nh
|
||||
.\" disable justification (adjust text to left margin only)
|
||||
.ad l
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * MAIN CONTENT STARTS HERE *
|
||||
.\" -----------------------------------------------------------------
|
||||
.SH "NAME"
|
||||
ansible-playbook \- run an ansible playbook
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
ansible\-playbook <filename\&.yml> \&... [options]
|
||||
.SH "DESCRIPTION"
|
||||
.sp
|
||||
\fBAnsible playbooks\fR are a configuration and multinode deployment system\&. Ansible\-playbook is the tool used to run them\&. See the project home page (link below) for more information\&.
|
||||
.SH "ARGUMENTS"
|
||||
.PP
|
||||
\fBfilename\&.yml\fR
|
||||
.RS 4
|
||||
The names of one or more YAML format files to run as ansible playbooks\&.
|
||||
.RE
|
||||
.SH "OPTIONS"
|
||||
.PP
|
||||
\fB\-v\fR, \fB\-\-verbose\fR
|
||||
.RS 4
|
||||
Verbose mode, more output from successful actions will be shown\&. Give up to three times for more output\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-i\fR \fIPATH\fR, \fB\-\-inventory=\fR\fIPATH\fR
|
||||
.RS 4
|
||||
The
|
||||
\fIPATH\fR
|
||||
to the inventory hosts file, which defaults to
|
||||
\fI/etc/ansible/hosts\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-M\fR \fIDIRECTORY\fR, \fB\-\-module\-path=\fR\fIDIRECTORY\fR
|
||||
.RS 4
|
||||
The
|
||||
\fIDIRECTORY\fR
|
||||
search path to load modules from\&. The default is
|
||||
\fI/usr/share/ansible\fR\&. This can also be set with the ANSIBLE_LIBRARY environment variable\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-e\fR \fIVARS\fR, \fB\-\-extra\-vars=\fR\fIVARS\fR
|
||||
.RS 4
|
||||
Extra variables to inject into a playbook, in key=value key=value format or as quoted JSON (hashes and arrays)\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-f\fR \fINUM\fR, \fB\-\-forks=\fR\fINUM\fR
|
||||
.RS 4
|
||||
Level of parallelism\&.
|
||||
\fINUM\fR
|
||||
is specified as an integer, the default is 5\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-k\fR, \fB\-\-ask\-pass\fR
|
||||
.RS 4
|
||||
Prompt for the SSH password instead of assuming key\-based authentication with ssh\-agent\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-K\fR, \fB\-\-ask\-sudo\-pass\fR
|
||||
.RS 4
|
||||
Prompt for the password to use for playbook plays that request sudo access, if any\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-U\fR, \fISUDO_USER\fR, \fB\-\-sudo\-user=\fR\fISUDO_USER\fR
|
||||
.RS 4
|
||||
Desired sudo user (default=root)\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-t\fR, \fITAGS\fR, \fB\-\-tags=\fR\fITAGS\fR
|
||||
.RS 4
|
||||
Only run plays and tasks tagged with these values\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-skip\-tags=\fR\fISKIP_TAGS\fR
|
||||
.RS 4
|
||||
Only run plays and tasks whose tags do not match these values\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-syntax\-check\fR
|
||||
.RS 4
|
||||
Look for syntax errors in the playbook, but don\(cqt run anything
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-check\fR
|
||||
.RS 4
|
||||
Do not make any changes on the remote system, but test resources to see what might have changed\&. Note this can not scan all possible resource types and is only a simulation\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-diff\fR
|
||||
.RS 4
|
||||
When changing any templated files, show the unified diffs of how they changed\&. When used with \-\-check, shows how the files would have changed if \-\-check were not used\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-T\fR \fISECONDS\fR, \fB\-\-timeout=\fR\fISECONDS\fR
|
||||
.RS 4
|
||||
Connection timeout to use when trying to talk to hosts, in
|
||||
\fISECONDS\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-s\fR, \fB\-\-sudo\fR
|
||||
.RS 4
|
||||
Force all plays to use sudo, even if not marked as such\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-u\fR \fIUSERNAME\fR, \fB\-\-user=\fR\fIUSERNAME\fR
|
||||
.RS 4
|
||||
Use this remote user name on playbook steps that do not indicate a user name to run as\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-c\fR \fICONNECTION\fR, \fB\-\-connection=\fR\fICONNECTION\fR
|
||||
.RS 4
|
||||
Connection type to use\&. Possible options are
|
||||
\fIparamiko\fR
|
||||
(SSH),
|
||||
\fIssh\fR, and
|
||||
\fIlocal\fR\&.
|
||||
\fIlocal\fR
|
||||
is mostly useful for crontab or kickstarts\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-l\fR \fISUBSET\fR, \fB\-\-limit=\fR\fISUBSET\fR
|
||||
.RS 4
|
||||
Further limits the selected host/group patterns\&.
|
||||
.RE
|
||||
.SH "ENVIRONMENT"
|
||||
.sp
|
||||
The following environment variables may be specified\&.
|
||||
.sp
|
||||
ANSIBLE_HOSTS \(em Override the default ansible hosts file
|
||||
.sp
|
||||
ANSIBLE_LIBRARY \(em Override the default ansible module library path
|
||||
.SH "FILES"
|
||||
.sp
|
||||
/etc/ansible/hosts \(em Default inventory file
|
||||
.sp
|
||||
/usr/share/ansible/ \(em Default module library
|
||||
.sp
|
||||
/etc/ansible/ansible\&.cfg \(em Config file, used if present
|
||||
.sp
|
||||
~/\&.ansible\&.cfg \(em User config file, overrides the default config if present
|
||||
.SH "AUTHOR"
|
||||
.sp
|
||||
Ansible was originally written by Michael DeHaan\&. See the AUTHORS file for a complete list of contributors\&.
|
||||
.SH "COPYRIGHT"
|
||||
.sp
|
||||
Copyright \(co 2012, Michael DeHaan
|
||||
.sp
|
||||
Ansible is released under the terms of the GPLv3 License\&.
|
||||
.SH "SEE ALSO"
|
||||
.sp
|
||||
\fBansible\fR(1), \fBansible\-pull\fR(1), \fBansible\-doc\fR(1)
|
||||
.sp
|
||||
Extensive documentation is available in the documentation site: http://docs\&.ansible\&.com\&. IRC and mailing list info can be found in file CONTRIBUTING\&.md, available in: https://github\&.com/ansible/ansible
|
||||
.SH "AUTHOR"
|
||||
.PP
|
||||
\fB:doctype:manpage\fR
|
||||
.RS 4
|
||||
Author.
|
||||
.RE
|
||||
@ -1,6 +1,6 @@
|
||||
ansible-playbook(1)
|
||||
===================
|
||||
:doctype: manpage
|
||||
:doctype:manpage
|
||||
:man source: Ansible
|
||||
:man version: %VERSION%
|
||||
:man manual: System administration commands
|
||||
@ -34,91 +34,16 @@ The names of one or more YAML format files to run as ansible playbooks.
|
||||
OPTIONS
|
||||
-------
|
||||
|
||||
*-b*, *--become*::
|
||||
*-v*, *--verbose*::
|
||||
|
||||
Use privilege escalation (specific one depends on become_method),
|
||||
this does not imply prompting for passwords.
|
||||
|
||||
*-K*, *--ask-become-pass*::
|
||||
|
||||
Ask for privilege escalation password.
|
||||
|
||||
*-k*, *--ask-pass*::
|
||||
|
||||
Prompt for the connection password, if it is needed for the transport used.
|
||||
For example, using ssh and not having a key-based authentication with ssh-agent.
|
||||
|
||||
*--ask-su-pass*::
|
||||
|
||||
Prompt for su password, used with --su (deprecated, use become).
|
||||
|
||||
*--ask-sudo-pass*::
|
||||
|
||||
Prompt for the password to use with --sudo, if any (deprecated, use become).
|
||||
|
||||
*--ask-vault-pass*::
|
||||
|
||||
Prompt for vault password.
|
||||
|
||||
*-C*, *--check*::
|
||||
|
||||
Do not make any changes on the remote system, but test resources to see what might
|
||||
have changed. Note this can not scan all possible resource types and is only
|
||||
a simulation.
|
||||
|
||||
*-c* 'CONNECTION', *--connection=*'CONNECTION'::
|
||||
|
||||
Connection type to use. Most common options are 'paramiko' (SSH), 'ssh', 'winrm'
|
||||
and 'local'. 'local' is mostly useful for crontab or kickstarts.
|
||||
|
||||
*-D*, *--diff*::
|
||||
|
||||
When changing any templated files, show the unified diffs of how they changed. When
|
||||
used with --check, shows how the files would have changed if --check were not used.
|
||||
|
||||
*-e* 'EXTRA_VARS', *--extra-vars=*'EXTRA_VARS'::
|
||||
|
||||
Extra variables to inject into a playbook, in key=value key=value format or
|
||||
as quoted YAML/JSON (hashes and arrays). To load variables from a file, specify
|
||||
the file preceded by @ (e.g. @vars.yml).
|
||||
|
||||
*--flush-cache*::
|
||||
|
||||
Clear the fact cache.
|
||||
|
||||
*--force-handlers*::
|
||||
|
||||
Run handlers even if a task fails.
|
||||
|
||||
*-f* 'NUM', *--forks=*'NUM'::
|
||||
|
||||
Level of parallelism. 'NUM' is specified as an integer, the default is 5.
|
||||
|
||||
*-h*, *--help*::
|
||||
|
||||
Show help page and exit.
|
||||
Verbose mode, more output from successful actions will be shown. Give
|
||||
up to three times for more output.
|
||||
|
||||
*-i* 'PATH', *--inventory=*'PATH'::
|
||||
|
||||
The 'PATH' to the inventory, which defaults to '/etc/ansible/hosts'.
|
||||
Alternatively, you can use a comma-separated list of hosts or a single host with a trailing comma 'host,'.
|
||||
The 'PATH' to the inventory hosts file, which defaults to
|
||||
'/etc/ansible/hosts'.
|
||||
|
||||
*-l* 'SUBSET', *--limit=*'SUBSET'::
|
||||
|
||||
Further limits the selected host/group patterns.
|
||||
You can prefix it with '~' to indicate that the pattern is a regex.
|
||||
|
||||
*--list-hosts*::
|
||||
|
||||
Outputs a list of matching hosts; does not execute anything else.
|
||||
|
||||
*--list-tags*::
|
||||
|
||||
List all available tags; does not execute anything else.
|
||||
|
||||
*--list-tasks*::
|
||||
|
||||
List all tasks that would be executed; does not execute anything else.
|
||||
|
||||
*-M* 'DIRECTORY', *--module-path=*'DIRECTORY'::
|
||||
|
||||
@ -126,51 +51,34 @@ The 'DIRECTORY' search path to load modules from. The default is
|
||||
'/usr/share/ansible'. This can also be set with the ANSIBLE_LIBRARY
|
||||
environment variable.
|
||||
|
||||
*--private-key=*'PRIVATE_KEY_FILE'::
|
||||
*-e* 'VARS', *--extra-vars=*'VARS'::
|
||||
|
||||
Use this file to authenticate the connection.
|
||||
Extra variables to inject into a playbook, in key=value key=value format or
|
||||
as quoted JSON (hashes and arrays).
|
||||
|
||||
*--start-at-task=*'START_AT'::
|
||||
*-f* 'NUM', *--forks=*'NUM'::
|
||||
|
||||
Start the playbook at the task matching this name.
|
||||
Level of parallelism. 'NUM' is specified as an integer, the default is 5.
|
||||
|
||||
*--step*::
|
||||
|
||||
One-step-at-a-time: confirm each task before running.
|
||||
*-k*, *--ask-pass*::
|
||||
|
||||
*-S*, --su*::
|
||||
Prompt for the SSH password instead of assuming key-based
|
||||
authentication with ssh-agent.
|
||||
|
||||
Run operations with su (deprecated, use become).
|
||||
|
||||
*-R SU-USER*, *--su-user=*'SU_USER'::
|
||||
*-K*, *--ask-sudo-pass*::
|
||||
|
||||
Run operations with su as this user (default=root) (deprecated, use become).
|
||||
Prompt for the password to use for playbook plays that request sudo
|
||||
access, if any.
|
||||
|
||||
*-s*, *--sudo*::
|
||||
*-U*, 'SUDO_USER', *--sudo-user=*'SUDO_USER'::
|
||||
|
||||
Run the command as the user given by -u and sudo to root (deprecated, use become).
|
||||
Desired sudo user (default=root).
|
||||
|
||||
*--ssh-common-args=*''-o ProxyCommand="ssh -W %h:%p ..." ...''::
|
||||
*-t*, 'TAGS', *--tags=*'TAGS'::
|
||||
|
||||
Add the specified arguments to any sftp/scp/ssh command-line. Useful to
|
||||
set a ProxyCommand to use a jump host, but any arguments that are
|
||||
accepted by all three programs may be specified.
|
||||
|
||||
*--sftp-extra-args=*''-f ...''::
|
||||
|
||||
Add the specified arguments to any sftp command-line.
|
||||
|
||||
*--scp-extra-args=*''-l ...''::
|
||||
|
||||
Add the specified arguments to any scp command-line.
|
||||
|
||||
*--ssh-extra-args=*''-R ...''::
|
||||
|
||||
Add the specified arguments to any ssh command-line.
|
||||
|
||||
*-U* 'SUDO_USERNAME', *--sudo-user=*'SUDO_USERNAME'::
|
||||
|
||||
Sudo to 'SUDO_USERNAME' default is root. (deprecated, use become).
|
||||
Only run plays and tasks tagged with these values.
|
||||
|
||||
*--skip-tags=*'SKIP_TAGS'::
|
||||
|
||||
@ -178,66 +86,53 @@ Only run plays and tasks whose tags do not match these values.
|
||||
|
||||
*--syntax-check*::
|
||||
|
||||
Look for syntax errors in the playbook, but don't run anything.
|
||||
Look for syntax errors in the playbook, but don't run anything
|
||||
|
||||
*-t*, 'TAGS', *--tags=*'TAGS'::
|
||||
*--check*::
|
||||
|
||||
Only run plays and tasks tagged with these values.
|
||||
Do not make any changes on the remote system, but test resources to see what might
|
||||
have changed. Note this can not scan all possible resource types and is only
|
||||
a simulation.
|
||||
|
||||
*--diff*::
|
||||
|
||||
When changing any templated files, show the unified diffs of how they changed. When
|
||||
used with --check, shows how the files would have changed if --check were not used.
|
||||
|
||||
*-T* 'SECONDS', *--timeout=*'SECONDS'::
|
||||
|
||||
Connection timeout to use when trying to talk to hosts, in 'SECONDS'.
|
||||
|
||||
|
||||
*-s*, *--sudo*::
|
||||
|
||||
Force all plays to use sudo, even if not marked as such.
|
||||
|
||||
|
||||
*-u* 'USERNAME', *--user=*'USERNAME'::
|
||||
|
||||
Use this 'USERNAME' to login to the target host, instead of the current user.
|
||||
Use this remote user name on playbook steps that do not indicate a
|
||||
user name to run as.
|
||||
|
||||
*--vault-password-file=*'VAULT_PASSWORD_FILE'::
|
||||
*-c* 'CONNECTION', *--connection=*'CONNECTION'::
|
||||
|
||||
Vault password file.
|
||||
Connection type to use. Possible options are 'paramiko' (SSH), 'ssh',
|
||||
and 'local'. 'local' is mostly useful for crontab or kickstarts.
|
||||
|
||||
*-v*, *--verbose*::
|
||||
*-l* 'SUBSET', *--limit=*'SUBSET'::
|
||||
|
||||
Verbose mode, more output from successful actions will be shown. Give
|
||||
up to three times for more output.
|
||||
Further limits the selected host/group patterns.
|
||||
|
||||
*--version*::
|
||||
|
||||
Show program's version number and exit.
|
||||
|
||||
EXIT STATUS
|
||||
-----------
|
||||
|
||||
*0* -- OK or no hosts matched
|
||||
|
||||
*1* -- Error
|
||||
|
||||
*2* -- One or more hosts failed
|
||||
|
||||
*3* -- One or more hosts were unreachable
|
||||
|
||||
*4* -- Parser error
|
||||
|
||||
*5* -- Bad or incomplete options
|
||||
|
||||
*99* -- User interrupted execution
|
||||
|
||||
*250* -- Unexpected error
|
||||
|
||||
ENVIRONMENT
|
||||
-----------
|
||||
|
||||
The following environment variables may be specified:
|
||||
The following environment variables may be specified.
|
||||
|
||||
ANSIBLE_INVENTORY -- Override the default ansible inventory file
|
||||
ANSIBLE_HOSTS -- Override the default ansible hosts file
|
||||
|
||||
ANSIBLE_LIBRARY -- Override the default ansible module library path
|
||||
|
||||
ANSIBLE_CONFIG -- Override the default ansible config file
|
||||
|
||||
Many more are available for most options in ansible.cfg
|
||||
|
||||
|
||||
FILES
|
||||
-----
|
||||
|
||||
@ -249,7 +144,6 @@ FILES
|
||||
|
||||
~/.ansible.cfg -- User config file, overrides the default config if present
|
||||
|
||||
|
||||
AUTHOR
|
||||
------
|
||||
|
||||
@ -268,7 +162,7 @@ Ansible is released under the terms of the GPLv3 License.
|
||||
SEE ALSO
|
||||
--------
|
||||
|
||||
*ansible*(1), *ansible-pull*(1), *ansible-doc*(1), *ansible-vault*(1), *ansible-galaxy*(1)
|
||||
*ansible*(1), *ansible-pull*(1), *ansible-doc*(1)
|
||||
|
||||
Extensive documentation is available in the documentation site:
|
||||
<http://docs.ansible.com>. IRC and mailing list info can be found
|
||||
|
||||
112
docs/man/man1/ansible-pull.1
Normal file
112
docs/man/man1/ansible-pull.1
Normal file
@ -0,0 +1,112 @@
|
||||
'\" t
|
||||
.\" Title: ansible
|
||||
.\" Author: :doctype:manpage
|
||||
.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
|
||||
.\" Date: 05/26/2014
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 1.7
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE" "1" "05/26/2014" "Ansible 1\&.7" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.\" http://bugs.debian.org/507673
|
||||
.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * set default formatting
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" disable hyphenation
|
||||
.nh
|
||||
.\" disable justification (adjust text to left margin only)
|
||||
.ad l
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * MAIN CONTENT STARTS HERE *
|
||||
.\" -----------------------------------------------------------------
|
||||
.SH "NAME"
|
||||
ansible-pull \- set up a remote copy of ansible on each managed node
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
ansible \-d DEST \-U URL [options] [ <filename\&.yml> ]
|
||||
.SH "DESCRIPTION"
|
||||
.sp
|
||||
\fBAnsible\fR is an extra\-simple tool/framework/API for doing \*(Aqremote things\*(Aq over SSH\&.
|
||||
.sp
|
||||
Use ansible\-pull to set up a remote copy of ansible on each managed node, each set to run via cron and update playbook source via a source repository\&. This inverts the default \fBpush\fR architecture of ansible into a \fBpull\fR architecture, which has near\-limitless scaling potential\&.
|
||||
.sp
|
||||
The setup playbook can be tuned to change the cron frequency, logging locations, and parameters to ansible\-pull\&.
|
||||
.sp
|
||||
This is useful both for extreme scale\-out as well as periodic remediation\&. Usage of the \fIfetch\fR module to retrieve logs from ansible\-pull runs would be an excellent way to gather and analyze remote logs from ansible\-pull\&.
|
||||
.SH "OPTIONAL ARGUMENT"
|
||||
.PP
|
||||
\fBfilename\&.yml\fR
|
||||
.RS 4
|
||||
The name of one the YAML format files to run as an ansible playbook\&. This can be a relative path within the checkout\&. If not provided, ansible\-pull will look for a playbook based on the host\(cqs fully\-qualified domain name, on the host hostname and finally a playbook named
|
||||
\fBlocal\&.yml\fR\&.
|
||||
.RE
|
||||
.SH "OPTIONS"
|
||||
.PP
|
||||
\fB\-d\fR \fIDEST\fR, \fB\-\-directory=\fR\fIDEST\fR
|
||||
.RS 4
|
||||
Directory to checkout repository into\&. If not provided, a subdirectory of ~/\&.ansible/pull/ will be used\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-U\fR \fIURL\fR, \fB\-\-url=\fR\fIURL\fR
|
||||
.RS 4
|
||||
URL of the playbook repository to checkout\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-C\fR \fICHECKOUT\fR, \fB\-\-checkout=\fR\fICHECKOUT\fR
|
||||
.RS 4
|
||||
Branch/Tag/Commit to checkout\&. If not provided, uses default behavior of module used to check out playbook repository\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-f\fR, \fB\-\-force\fR
|
||||
.RS 4
|
||||
Force running of playbook even if unable to update playbook repository\&. This can be useful, for example, to enforce run\-time state when a network connection may not always be up or possible\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-i\fR \fIPATH\fR, \fB\-\-inventory=\fR\fIPATH\fR
|
||||
.RS 4
|
||||
The
|
||||
\fIPATH\fR
|
||||
to the inventory hosts file\&. This can be a relative path within the checkout\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-purge\fR
|
||||
.RS 4
|
||||
Purge the checkout after the playbook is run\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-m\fR \fINAME\fR, \fB\-\-module\-name=\fR\fINAME\fR
|
||||
.RS 4
|
||||
Module used to checkout playbook repository\&. Defaults to git\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-o\fR, \fB\-\-only\-if\-changed\fR
|
||||
.RS 4
|
||||
Run the playbook only if the repository has changed
|
||||
.RE
|
||||
.SH "AUTHOR"
|
||||
.sp
|
||||
Ansible was originally written by Michael DeHaan\&. See the AUTHORS file for a complete list of contributors\&.
|
||||
.SH "COPYRIGHT"
|
||||
.sp
|
||||
Copyright \(co 2012, Michael DeHaan
|
||||
.sp
|
||||
Ansible is released under the terms of the GPLv3 License\&.
|
||||
.SH "SEE ALSO"
|
||||
.sp
|
||||
\fBansible\fR(1), \fBansible\-playbook\fR(1), \fBansible\-doc\fR(1)
|
||||
.sp
|
||||
Extensive documentation is available in the documentation site: http://docs\&.ansible\&.com\&. IRC and mailing list info can be found in file CONTRIBUTING\&.md, available in: https://github\&.com/ansible/ansible
|
||||
.SH "AUTHOR"
|
||||
.PP
|
||||
\fB:doctype:manpage\fR
|
||||
.RS 4
|
||||
Author.
|
||||
.RE
|
||||
@ -1,25 +1,25 @@
|
||||
ansible(1)
|
||||
=========
|
||||
:doctype: manpage
|
||||
:doctype:manpage
|
||||
:man source: Ansible
|
||||
:man version: %VERSION%
|
||||
:man manual: System administration commands
|
||||
|
||||
|
||||
NAME
|
||||
----
|
||||
ansible-pull - pull playbooks from VCS server and run them using this machine as the target.
|
||||
ansible-pull - set up a remote copy of ansible on each managed node
|
||||
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
ansible-pull -U URL [options] [ <filename.yml> ]
|
||||
ansible -d DEST -U URL [options] [ <filename.yml> ]
|
||||
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
|
||||
*Ansible* is an extra-simple tool/framework/API for doing \'remote things'.
|
||||
*Ansible* is an extra-simple tool/framework/API for doing \'remote things' over
|
||||
SSH.
|
||||
|
||||
Use ansible-pull to set up a remote copy of ansible on each managed
|
||||
node, each set to run via cron and update playbook source via
|
||||
@ -50,49 +50,19 @@ host hostname and finally a playbook named *local.yml*.
|
||||
OPTIONS
|
||||
-------
|
||||
|
||||
*--accept-host-key*::
|
||||
*-d* 'DEST', *--directory=*'DEST'::
|
||||
|
||||
Adds the hostkey for the repo URL if not already added.
|
||||
Directory to checkout repository into. If not provided, a subdirectory of
|
||||
~/.ansible/pull/ will be used.
|
||||
|
||||
*-b*, *--become*::
|
||||
*-U* 'URL', *--url=*'URL'::
|
||||
|
||||
Use privilege escalation (specific one depends on become_method),
|
||||
this does not imply prompting for passwords.
|
||||
|
||||
*-K*, *--ask-become-pass*::
|
||||
|
||||
Ask for privilege escalation password.
|
||||
|
||||
*-k*, *--ask-pass*::
|
||||
|
||||
Prompt for the connection password, if it is needed for the transport used.
|
||||
For example, using ssh and not having a key-based authentication with ssh-agent.
|
||||
|
||||
*--ask-su-pass*::
|
||||
|
||||
Prompt for su password, used with --su (deprecated, use become).
|
||||
|
||||
*--ask-sudo-pass*::
|
||||
|
||||
Prompt for the password to use with --sudo, if any (deprecated, use become).
|
||||
|
||||
*--ask-vault-pass*::
|
||||
|
||||
Prompt for vault password.
|
||||
URL of the playbook repository to checkout.
|
||||
|
||||
*-C* 'CHECKOUT', *--checkout=*'CHECKOUT'::
|
||||
|
||||
Branch/Tag/Commit to checkout. If not provided, uses default behavior of module used to check out playbook repository.
|
||||
|
||||
*-d* 'DEST', *--directory=*'DEST'::
|
||||
|
||||
Directory to checkout repository into. If not provided, a subdirectory of ~/.ansible/pull/ will be used.
|
||||
|
||||
*-e* 'EXTRA_VARS', *--extra-vars=*'EXTRA_VARS::
|
||||
|
||||
Extra variables to inject into a playbook, in key=value key=value format or
|
||||
as quoted YAML/JSON (hashes and arrays). To load variables from a file, specify
|
||||
the file preceded by @ (e.g. @vars.yml).
|
||||
Branch/Tag/Commit to checkout. If not provided, uses default behavior
|
||||
of module used to check out playbook repository.
|
||||
|
||||
*-f*, *--force*::
|
||||
|
||||
@ -100,22 +70,14 @@ Force running of playbook even if unable to update playbook repository. This
|
||||
can be useful, for example, to enforce run-time state when a network
|
||||
connection may not always be up or possible.
|
||||
|
||||
*--full*::
|
||||
|
||||
Do a full clone of the repository. By default ansible-pull will do a shallow clone based on the last revision.
|
||||
|
||||
*-h*, *--help*::
|
||||
|
||||
Show the help message and exit.
|
||||
|
||||
*-i* 'PATH', *--inventory=*'PATH'::
|
||||
|
||||
The 'PATH' to the inventory, which defaults to '/etc/ansible/hosts'.
|
||||
Alternatively you can use a comma separated list of hosts or single host with traling comma 'host,'.
|
||||
The 'PATH' to the inventory hosts file. This can be a relative path within
|
||||
the checkout.
|
||||
|
||||
*--private-key=*'PRIVATE_KEY_FILE'::
|
||||
*--purge*::
|
||||
|
||||
Use this file to authenticate the connection.
|
||||
Purge the checkout after the playbook is run.
|
||||
|
||||
*-m* 'NAME', *--module-name=*'NAME'::
|
||||
|
||||
@ -123,115 +85,27 @@ Module used to checkout playbook repository. Defaults to git.
|
||||
|
||||
*-o*, *--only-if-changed*::
|
||||
|
||||
Only run the playbook if the repository has been updated.
|
||||
|
||||
*--purge*::
|
||||
|
||||
Purge the checkout after the playbook is run.
|
||||
|
||||
*-s* 'SLEEP', *--sleep=*'SLEEP'::
|
||||
|
||||
Sleep for random interval (between 0 and SLEEP number of seconds) before starting. This is a useful way ot disperse git requests.
|
||||
|
||||
*--ssh-common-args=*''-o ProxyCommand="ssh -W %h:%p ..." ...''::
|
||||
|
||||
Add the specified arguments to any sftp/scp/ssh command-line. Useful to
|
||||
set a ProxyCommand to use a jump host, but any arguments that are
|
||||
accepted by all three programs may be specified.
|
||||
|
||||
*--sftp-extra-args=*''-f ...''::
|
||||
|
||||
Add the specified arguments to any sftp command-line.
|
||||
|
||||
*--scp-extra-args=*''-l ...''::
|
||||
|
||||
Add the specified arguments to any scp command-line.
|
||||
|
||||
*--ssh-extra-args=*''-R ...''::
|
||||
|
||||
Add the specified arguments to any ssh command-line.
|
||||
|
||||
*-t* 'TAGS', *--tags=*'TAGS'::
|
||||
|
||||
Only run plays and tasks tagged with these values.
|
||||
|
||||
*-U* 'URL', *--url=*'URL'::
|
||||
|
||||
URL of the playbook repository to checkout.
|
||||
|
||||
*--vault-password-file=*'VAULT_PASSWORD_FILE'::
|
||||
|
||||
Vault password file.
|
||||
|
||||
*--clean*::
|
||||
|
||||
Modified files in the working repository will be discarded.
|
||||
|
||||
*--track-subs*::
|
||||
|
||||
Submodules will track the latest changes.
|
||||
|
||||
*-v*, *--verbose*::
|
||||
|
||||
Pass -vvv to ansible-playbook.
|
||||
|
||||
|
||||
INVENTORY
|
||||
---------
|
||||
|
||||
Ansible stores the hosts it can potentially operate on in an inventory.
|
||||
This can be an ini-like file, a script, directory or a list.
|
||||
The ini syntax is one host per line. Groups headers are allowed and
|
||||
are included on their own line, enclosed in square brackets that start the line.
|
||||
|
||||
Ranges of hosts are also supported. For more information and
|
||||
additional options, see the documentation on http://docs.ansible.com/.
|
||||
|
||||
|
||||
ENVIRONMENT
|
||||
-----------
|
||||
|
||||
The following environment variables may be specified.
|
||||
|
||||
ANSIBLE_INVENTORY -- Override the default ansible inventory file
|
||||
|
||||
ANSIBLE_LIBRARY -- Override the default ansible module library path
|
||||
|
||||
ANSIBLE_CONFIG -- Override the default ansible config file
|
||||
|
||||
Many more are available for most options in ansible.cfg
|
||||
|
||||
|
||||
FILES
|
||||
-----
|
||||
|
||||
/etc/ansible/hosts -- Default inventory file
|
||||
|
||||
/usr/share/ansible/ -- Default module library
|
||||
|
||||
/etc/ansible/ansible.cfg -- Config file, used if present
|
||||
|
||||
~/.ansible.cfg -- User config file, overrides the default config if present
|
||||
|
||||
Run the playbook only if the repository has changed
|
||||
|
||||
AUTHOR
|
||||
------
|
||||
|
||||
Ansible was originally written by Michael DeHaan.
|
||||
See the AUTHORS file for a complete list of contributors.
|
||||
Ansible was originally written by Michael DeHaan. See the AUTHORS file
|
||||
for a complete list of contributors.
|
||||
|
||||
|
||||
COPYRIGHT
|
||||
---------
|
||||
|
||||
Copyright © 2012, Michael DeHaan
|
||||
|
||||
Ansible is released under the terms of the GPLv3 License.
|
||||
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
|
||||
*ansible*(1) *ansible-playbook*(1), *ansible-doc*(1), *ansible-vault*(1), *ansible-galaxy*(1)
|
||||
*ansible*(1), *ansible-playbook*(1), *ansible-doc*(1)
|
||||
|
||||
Extensive documentation is available in the documentation site:
|
||||
<http://docs.ansible.com>. IRC and mailing list info can be found
|
||||
|
||||
103
docs/man/man1/ansible-vault.1
Normal file
103
docs/man/man1/ansible-vault.1
Normal file
@ -0,0 +1,103 @@
|
||||
'\" t
|
||||
.\" Title: ansible-vault
|
||||
.\" Author: [see the "AUTHOR" section]
|
||||
.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
|
||||
.\" Date: 05/26/2014
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 1.7
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE\-VAULT" "1" "05/26/2014" "Ansible 1\&.7" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.\" http://bugs.debian.org/507673
|
||||
.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * set default formatting
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" disable hyphenation
|
||||
.nh
|
||||
.\" disable justification (adjust text to left margin only)
|
||||
.ad l
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * MAIN CONTENT STARTS HERE *
|
||||
.\" -----------------------------------------------------------------
|
||||
.SH "NAME"
|
||||
ansible-vault \- manage encrypted YAML data\&.
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
ansible\-vault [create|decrypt|edit|encrypt|rekey] [\-\-help] [options] file_name
|
||||
.SH "DESCRIPTION"
|
||||
.sp
|
||||
\fBansible\-vault\fR can encrypt any structured data file used by Ansible\&. This can include \fBgroup_vars/\fR or \fBhost_vars/\fR inventory variables, variables loaded by \fBinclude_vars\fR or \fBvars_files\fR, or variable files passed on the ansible\-playbook command line with \fB\-e @file\&.yml\fR or \fB\-e @file\&.json\fR\&. Role variables and defaults are also included!
|
||||
.sp
|
||||
Because Ansible tasks, handlers, and so on are also data, these can also be encrypted with vault\&. If you\(cqd like to not betray what variables you are even using, you can go as far to keep an individual task file entirely encrypted\&.
|
||||
.SH "COMMON OPTIONS"
|
||||
.sp
|
||||
The following options are available to all sub\-commands:
|
||||
.PP
|
||||
\fB\-\-vault\-password\-file=\fR\fIFILE\fR
|
||||
.RS 4
|
||||
A file containing the vault password to be used during the encryption/decryption steps\&. Be sure to keep this file secured if it is used\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-h\fR, \fB\-\-help\fR
|
||||
.RS 4
|
||||
Show a help message related to the given sub\-command\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-debug\fR
|
||||
.RS 4
|
||||
Enable debugging output for troubleshooting\&.
|
||||
.RE
|
||||
.SH "CREATE"
|
||||
.sp
|
||||
\fB$ ansible\-vault create [options] FILE\fR
|
||||
.sp
|
||||
The \fBcreate\fR sub\-command is used to initialize a new encrypted file\&.
|
||||
.sp
|
||||
First you will be prompted for a password\&. The password used with vault currently must be the same for all files you wish to use together at the same time\&.
|
||||
.sp
|
||||
After providing a password, the tool will launch whatever editor you have defined with $EDITOR, and defaults to vim\&. Once you are done with the editor session, the file will be saved as encrypted data\&.
|
||||
.sp
|
||||
The default cipher is AES (which is shared\-secret based)\&.
|
||||
.SH "EDIT"
|
||||
.sp
|
||||
\fB$ ansible\-vault edit [options] FILE\fR
|
||||
.sp
|
||||
The \fBedit\fR sub\-command is used to modify a file which was previously encrypted using ansible\-vault\&.
|
||||
.sp
|
||||
This command will decrypt the file to a temporary file and allow you to edit the file, saving it back when done and removing the temporary file\&.
|
||||
.SH "REKEY"
|
||||
.sp
|
||||
*$ ansible\-vault rekey [options] FILE_1 [FILE_2, \&..., FILE_N]
|
||||
.sp
|
||||
The \fBrekey\fR command is used to change the password on a vault\-encrypted files\&. This command can update multiple files at once, and will prompt for both the old and new passwords before modifying any data\&.
|
||||
.SH "ENCRYPT"
|
||||
.sp
|
||||
*$ ansible\-vault encrypt [options] FILE_1 [FILE_2, \&..., FILE_N]
|
||||
.sp
|
||||
The \fBencrypt\fR sub\-command is used to encrypt pre\-existing data files\&. As with the \fBrekey\fR command, you can specify multiple files in one command\&.
|
||||
.SH "DECRYPT"
|
||||
.sp
|
||||
*$ ansible\-vault decrypt [options] FILE_1 [FILE_2, \&..., FILE_N]
|
||||
.sp
|
||||
The \fBdecrypt\fR sub\-command is used to remove all encryption from data files\&. The files will be stored as plain\-text YAML once again, so be sure that you do not run this command on data files with active passwords or other sensitive data\&. In most cases, users will want to use the \fBedit\fR sub\-command to modify the files securely\&.
|
||||
.SH "AUTHOR"
|
||||
.sp
|
||||
Ansible was originally written by Michael DeHaan\&. See the AUTHORS file for a complete list of contributors\&.
|
||||
.SH "COPYRIGHT"
|
||||
.sp
|
||||
Copyright \(co 2014, Michael DeHaan
|
||||
.sp
|
||||
Ansible is released under the terms of the GPLv3 License\&.
|
||||
.SH "SEE ALSO"
|
||||
.sp
|
||||
\fBansible\fR(1), \fBansible\-pull\fR(1), \fBansible\-doc\fR(1)
|
||||
.sp
|
||||
Extensive documentation is available in the documentation site: http://docs\&.ansible\&.com\&. IRC and mailing list info can be found in file CONTRIBUTING\&.md, available in: https://github\&.com/ansible/ansible
|
||||
@ -7,7 +7,7 @@ ansible-vault(1)
|
||||
|
||||
NAME
|
||||
----
|
||||
ansible-vault - manage encrypted ansible vars files (YAML).
|
||||
ansible-vault - manage encrypted YAML data.
|
||||
|
||||
|
||||
SYNOPSIS
|
||||
@ -18,16 +18,15 @@ ansible-vault [create|decrypt|edit|encrypt|rekey] [--help] [options] file_name
|
||||
DESCRIPTION
|
||||
-----------
|
||||
|
||||
*ansible-vault* can encrypt any structured data file used by Ansible.
|
||||
This can include *group_vars/* or *host_vars/* inventory variables,
|
||||
variables loaded by *include_vars* or *vars_files*, or variable files
|
||||
passed on the ansible-playbook command line with *-e @file.yml* or *-e @file.json*.
|
||||
Role variables and defaults are also included!
|
||||
*ansible-vault* can encrypt any structured data file used by Ansible. This can include
|
||||
*group_vars/* or *host_vars/* inventory variables, variables loaded by *include_vars* or
|
||||
*vars_files*, or variable files passed on the ansible-playbook command line with
|
||||
*-e @file.yml* or *-e @file.json*. Role variables and defaults are also included!
|
||||
|
||||
Because Ansible tasks, handlers, and so on are also data, these can also be encrypted with vault.
|
||||
If you’d like to not betray what variables you are even using, you can go as far to keep an individual task file entirely encrypted.
|
||||
Because Ansible tasks, handlers, and so on are also data, these can also be encrypted with
|
||||
vault. If you’d like to not betray what variables you are even using, you can go as far to
|
||||
keep an individual task file entirely encrypted.
|
||||
|
||||
The password used with vault currently must be the same for all files you wish to use together at the same time.
|
||||
|
||||
COMMON OPTIONS
|
||||
--------------
|
||||
@ -37,33 +36,28 @@ The following options are available to all sub-commands:
|
||||
*--vault-password-file=*'FILE'::
|
||||
|
||||
A file containing the vault password to be used during the encryption/decryption
|
||||
steps. Be sure to keep this file secured if it is used. If the file is executable,
|
||||
it will be run and its standard output will be used as the password.
|
||||
|
||||
*--new-vault-password-file=*'FILE'::
|
||||
|
||||
A file containing the new vault password to be used when rekeying a
|
||||
file. Be sure to keep this file secured if it is used. If the file
|
||||
is executable, it will be run and its standard output will be used as
|
||||
the password.
|
||||
steps. Be sure to keep this file secured if it is used.
|
||||
|
||||
*-h*, *--help*::
|
||||
|
||||
Show a help message related to the given sub-command.
|
||||
|
||||
*--debug*::
|
||||
|
||||
If '--vault-password-file' is not supplied ansible-vault will automatically prompt for passwords as required.
|
||||
|
||||
Enable debugging output for troubleshooting.
|
||||
|
||||
CREATE
|
||||
------
|
||||
|
||||
*$ ansible-vault create [options] FILE*
|
||||
|
||||
The *create* sub-command is used to initialize a new encrypted file.
|
||||
The *create* sub-command is used to initialize a new encrypted file.
|
||||
|
||||
After providing a password, the tool will launch whatever editor you have defined
|
||||
with $EDITOR, and defaults to vi. Once you are done with the editor session, the
|
||||
First you will be prompted for a password. The password used with vault currently
|
||||
must be the same for all files you wish to use together at the same time.
|
||||
|
||||
After providing a password, the tool will launch whatever editor you have defined
|
||||
with $EDITOR, and defaults to vim. Once you are done with the editor session, the
|
||||
file will be saved as encrypted data.
|
||||
|
||||
The default cipher is AES (which is shared-secret based).
|
||||
@ -73,62 +67,39 @@ EDIT
|
||||
|
||||
*$ ansible-vault edit [options] FILE*
|
||||
|
||||
The *edit* sub-command is used to modify a file which was previously encrypted using ansible-vault.
|
||||
|
||||
This command will decrypt the file to a temporary file and allow you to edit the file,
|
||||
saving it back when done and removing the temporary file.
|
||||
The *edit* sub-command is used to modify a file which was previously encrypted
|
||||
using ansible-vault.
|
||||
|
||||
This command will decrypt the file to a temporary file and allow you to edit the
|
||||
file, saving it back when done and removing the temporary file.
|
||||
|
||||
REKEY
|
||||
-----
|
||||
|
||||
*$ ansible-vault rekey [options] FILE_1 [FILE_2, ..., FILE_N]*
|
||||
*$ ansible-vault rekey [options] FILE_1 [FILE_2, ..., FILE_N]
|
||||
|
||||
The *rekey* command is used to change the password on a vault-encrypted files.
|
||||
This command can update multiple files at once.
|
||||
|
||||
This command can update multiple files at once, and will prompt for both the
|
||||
old and new passwords before modifying any data.
|
||||
|
||||
ENCRYPT
|
||||
-------
|
||||
|
||||
*$ ansible-vault encrypt [options] FILE_1 [FILE_2, ..., FILE_N]*
|
||||
*$ ansible-vault encrypt [options] FILE_1 [FILE_2, ..., FILE_N]
|
||||
|
||||
The *encrypt* sub-command is used to encrypt pre-existing data files.
|
||||
As with the *rekey* command, you can specify multiple files in one command.
|
||||
|
||||
The *encrypt* command accepts an *--output FILENAME* option to determine where
|
||||
encrypted output is stored. With this option, input is read from the (at most one)
|
||||
filename given on the command line; if no input file is given, input is read from stdin.
|
||||
Either the input or the output file may be given as '-' for stdin and stdout respectively.
|
||||
If neither input nor output file is given, the command acts as a filter,
|
||||
reading plaintext from stdin and writing it to stdout.
|
||||
|
||||
Thus any of the following invocations can be used:
|
||||
|
||||
*$ ansible-vault encrypt*
|
||||
|
||||
*$ ansible-vault encrypt --output OUTFILE*
|
||||
|
||||
*$ ansible-vault encrypt INFILE --output OUTFILE*
|
||||
|
||||
*$ echo secret|ansible-vault encrypt --output OUTFILE*
|
||||
|
||||
Reading from stdin and writing only encrypted output is a good way to prevent
|
||||
sensitive data from ever hitting disk (either interactively or from a script).
|
||||
The *encrypt* sub-command is used to encrypt pre-existing data files. As with the
|
||||
*rekey* command, you can specify multiple files in one command.
|
||||
|
||||
DECRYPT
|
||||
-------
|
||||
|
||||
*$ ansible-vault decrypt [options] FILE_1 [FILE_2, ..., FILE_N]*
|
||||
*$ ansible-vault decrypt [options] FILE_1 [FILE_2, ..., FILE_N]
|
||||
|
||||
The *decrypt* sub-command is used to remove all encryption from data files.
|
||||
The files will be stored as plain-text YAML once again, so be sure that you do not run this
|
||||
command on data files with active passwords or other sensitive data.
|
||||
In most cases, users will want to use the *edit* sub-command to modify the files securely.
|
||||
The *decrypt* sub-command is used to remove all encryption from data files. The files
|
||||
will be stored as plain-text YAML once again, so be sure that you do not run this
|
||||
command on data files with active passwords or other sensitive data. In most cases,
|
||||
users will want to use the *edit* sub-command to modify the files securely.
|
||||
|
||||
As with *encrypt*, the *decrypt* subcommand also accepts the *--output FILENAME*
|
||||
option to specify where plaintext output is stored, and stdin/stdout is handled
|
||||
as described above.
|
||||
|
||||
AUTHOR
|
||||
------
|
||||
@ -148,7 +119,7 @@ Ansible is released under the terms of the GPLv3 License.
|
||||
SEE ALSO
|
||||
--------
|
||||
|
||||
*ansible*(1), *ansible-pull*(1), *ansible-doc*(1), *ansible-playbook*(1), *ansible-galaxy*(1)
|
||||
*ansible*(1), *ansible-pull*(1), *ansible-doc*(1)
|
||||
|
||||
Extensive documentation is available in the documentation site:
|
||||
<http://docs.ansible.com>. IRC and mailing list info can be found
|
||||
|
||||
214
docs/man/man1/ansible.1
Normal file
214
docs/man/man1/ansible.1
Normal file
@ -0,0 +1,214 @@
|
||||
'\" t
|
||||
.\" Title: ansible
|
||||
.\" Author: :doctype:manpage
|
||||
.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
|
||||
.\" Date: 05/26/2014
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 1.7
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE" "1" "05/26/2014" "Ansible 1\&.7" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.\" http://bugs.debian.org/507673
|
||||
.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
|
||||
.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * set default formatting
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" disable hyphenation
|
||||
.nh
|
||||
.\" disable justification (adjust text to left margin only)
|
||||
.ad l
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * MAIN CONTENT STARTS HERE *
|
||||
.\" -----------------------------------------------------------------
|
||||
.SH "NAME"
|
||||
ansible \- run a command somewhere else
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
ansible <host\-pattern> [\-f forks] [\-m module_name] [\-a args]
|
||||
.SH "DESCRIPTION"
|
||||
.sp
|
||||
\fBAnsible\fR is an extra\-simple tool/framework/API for doing \*(Aqremote things\*(Aq over SSH\&.
|
||||
.SH "ARGUMENTS"
|
||||
.PP
|
||||
\fBhost\-pattern\fR
|
||||
.RS 4
|
||||
A name of a group in the inventory file, a shell\-like glob selecting hosts in inventory file, or any combination of the two separated by semicolons\&.
|
||||
.RE
|
||||
.SH "OPTIONS"
|
||||
.PP
|
||||
\fB\-v\fR, \fB\-\-verbose\fR
|
||||
.RS 4
|
||||
Verbose mode, more output from successful actions will be shown\&. Give up to three times for more output\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-i\fR \fIPATH\fR, \fB\-\-inventory=\fR\fIPATH\fR
|
||||
.RS 4
|
||||
The
|
||||
\fIPATH\fR
|
||||
to the inventory hosts file, which defaults to
|
||||
\fI/etc/ansible/hosts\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-f\fR \fINUM\fR, \fB\-\-forks=\fR\fINUM\fR
|
||||
.RS 4
|
||||
Level of parallelism\&.
|
||||
\fINUM\fR
|
||||
is specified as an integer, the default is 5\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-private\-key=\fR\fIPRIVATE_KEY_FILE\fR
|
||||
.RS 4
|
||||
Use this file to authenticate the connection\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-m\fR \fINAME\fR, \fB\-\-module\-name=\fR\fINAME\fR
|
||||
.RS 4
|
||||
Execute the module called
|
||||
\fINAME\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-M\fR \fIDIRECTORY\fR, \fB\-\-module\-path=\fR\fIDIRECTORY\fR
|
||||
.RS 4
|
||||
The
|
||||
\fIDIRECTORY\fR
|
||||
search path to load modules from\&. The default is
|
||||
\fI/usr/share/ansible\fR\&. This can also be set with the ANSIBLE_LIBRARY environment variable\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-a\fR \*(Aq\fIARGUMENTS\fR\*(Aq, \fB\-\-args=\fR\*(Aq\fIARGUMENTS\fR\*(Aq
|
||||
.RS 4
|
||||
The
|
||||
\fIARGUMENTS\fR
|
||||
to pass to the module\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-k\fR, \fB\-\-ask\-pass\fR
|
||||
.RS 4
|
||||
Prompt for the SSH password instead of assuming key\-based authentication with ssh\-agent\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-K\fR, \fB\-\-ask\-sudo\-pass\fR
|
||||
.RS 4
|
||||
Prompt for the password to use with \-\-sudo, if any
|
||||
.RE
|
||||
.PP
|
||||
\fB\-o\fR, \fB\-\-one\-line\fR
|
||||
.RS 4
|
||||
Try to output everything on one line\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-s\fR, \fB\-\-sudo\fR
|
||||
.RS 4
|
||||
Run the command as the user given by \-u and sudo to root\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-t\fR \fIDIRECTORY\fR, \fB\-\-tree=\fR\fIDIRECTORY\fR
|
||||
.RS 4
|
||||
Save contents in this output
|
||||
\fIDIRECTORY\fR, with the results saved in a file named after each host\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-T\fR \fISECONDS\fR, \fB\-\-timeout=\fR\fISECONDS\fR
|
||||
.RS 4
|
||||
Connection timeout to use when trying to talk to hosts, in
|
||||
\fISECONDS\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-B\fR \fINUM\fR, \fB\-\-background=\fR\fINUM\fR
|
||||
.RS 4
|
||||
Run commands in the background, killing the task after
|
||||
\fINUM\fR
|
||||
seconds\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-P\fR \fINUM\fR, \fB\-\-poll=\fR\fINUM\fR
|
||||
.RS 4
|
||||
Poll a background job every
|
||||
\fINUM\fR
|
||||
seconds\&. Requires
|
||||
\fB\-B\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-u\fR \fIUSERNAME\fR, \fB\-\-user=\fR\fIUSERNAME\fR
|
||||
.RS 4
|
||||
Use this remote
|
||||
\fIUSERNAME\fR
|
||||
instead of the current user\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-U\fR \fISUDO_USERNAME\fR, \fB\-\-sudo\-user=\fR\fISUDO_USERNAME\fR
|
||||
.RS 4
|
||||
Sudo to
|
||||
\fISUDO_USERNAME\fR
|
||||
instead of root\&. Implies \-\-sudo\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-c\fR \fICONNECTION\fR, \fB\-\-connection=\fR\fICONNECTION\fR
|
||||
.RS 4
|
||||
Connection type to use\&. Possible options are
|
||||
\fIparamiko\fR
|
||||
(SSH),
|
||||
\fIssh\fR, and
|
||||
\fIlocal\fR\&.
|
||||
\fIlocal\fR
|
||||
is mostly useful for crontab or kickstarts\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-l\fR \fISUBSET\fR, \fB\-\-limit=\fR\fISUBSET\fR
|
||||
.RS 4
|
||||
Further limits the selected host/group patterns\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-l\fR \fI~REGEX\fR, \fB\-\-limit=\fR\fI~REGEX\fR
|
||||
.RS 4
|
||||
Further limits hosts with a regex pattern\&.
|
||||
.RE
|
||||
.SH "INVENTORY"
|
||||
.sp
|
||||
Ansible stores the hosts it can potentially operate on in an inventory file\&. The syntax is one host per line\&. Groups headers are allowed and are included on their own line, enclosed in square brackets that start the line\&.
|
||||
.sp
|
||||
Ranges of hosts are also supported\&. For more information and additional options, see the documentation on http://docs\&.ansible\&.com/\&.
|
||||
.SH "FILES"
|
||||
.sp
|
||||
/etc/ansible/hosts \(em Default inventory file
|
||||
.sp
|
||||
/usr/share/ansible/ \(em Default module library
|
||||
.sp
|
||||
/etc/ansible/ansible\&.cfg \(em Config file, used if present
|
||||
.sp
|
||||
~/\&.ansible\&.cfg \(em User config file, overrides the default config if present
|
||||
.SH "ENVIRONMENT"
|
||||
.sp
|
||||
The following environment variables may be specified\&.
|
||||
.sp
|
||||
ANSIBLE_HOSTS \(em Override the default ansible hosts file
|
||||
.sp
|
||||
ANSIBLE_LIBRARY \(em Override the default ansible module library path
|
||||
.sp
|
||||
ANSIBLE_CONFIG \(em Override the default ansible config file
|
||||
.SH "AUTHOR"
|
||||
.sp
|
||||
Ansible was originally written by Michael DeHaan\&. See the AUTHORS file for a complete list of contributors\&.
|
||||
.SH "COPYRIGHT"
|
||||
.sp
|
||||
Copyright \(co 2012, Michael DeHaan
|
||||
.sp
|
||||
Ansible is released under the terms of the GPLv3 License\&.
|
||||
.SH "SEE ALSO"
|
||||
.sp
|
||||
\fBansible\-playbook\fR(1), \fBansible\-pull\fR(1), \fBansible\-doc\fR(1)
|
||||
.sp
|
||||
Extensive documentation is available in the documentation site: http://docs\&.ansible\&.com\&. IRC and mailing list info can be found in file CONTRIBUTING\&.md, available in: https://github\&.com/ansible/ansible
|
||||
.SH "AUTHOR"
|
||||
.PP
|
||||
\fB:doctype:manpage\fR
|
||||
.RS 4
|
||||
Author.
|
||||
.RE
|
||||
@ -1,24 +1,25 @@
|
||||
ansible(1)
|
||||
=========
|
||||
:doctype:manpage
|
||||
:man source: Ansible
|
||||
:man version: %VERSION%
|
||||
:man manual: System administration commands
|
||||
|
||||
NAME
|
||||
----
|
||||
ansible - run a task on a target host(s)
|
||||
ansible - run a command somewhere else
|
||||
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
ansible <host-pattern> [-m module_name] [-a args] [options]
|
||||
ansible <host-pattern> [-f forks] [-m module_name] [-a args]
|
||||
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
|
||||
*Ansible* is an extra-simple tool/framework/API for doing \'remote things'.
|
||||
This is the adhoc command that allows for a \'single task playbook' run.
|
||||
*Ansible* is an extra-simple tool/framework/API for doing \'remote things' over
|
||||
SSH.
|
||||
|
||||
|
||||
ARGUMENTS
|
||||
@ -26,149 +27,62 @@ ARGUMENTS
|
||||
|
||||
*host-pattern*::
|
||||
|
||||
A name of a group in the inventory, a shell-like glob selecting
|
||||
hosts in inventory or any combination of the two separated by commas.
|
||||
A name of a group in the inventory file, a shell-like glob selecting
|
||||
hosts in inventory file, or any combination of the two separated by
|
||||
semicolons.
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
|
||||
*-a* \'_ARGUMENTS_', *--args=*\'_ARGUMENTS_'::
|
||||
*-v*, *--verbose*::
|
||||
|
||||
The 'ARGUMENTS' to pass to the module.
|
||||
Verbose mode, more output from successful actions will be shown. Give
|
||||
up to three times for more output.
|
||||
|
||||
*-b*, *--become*::
|
||||
*-i* 'PATH', *--inventory=*'PATH'::
|
||||
|
||||
Use privilege escalation (specific one depends on become_method),
|
||||
this does not imply prompting for passwords.
|
||||
The 'PATH' to the inventory hosts file, which defaults to '/etc/ansible/hosts'.
|
||||
|
||||
*-K*, *--ask-become-pass*::
|
||||
|
||||
Ask for privilege escalation password.
|
||||
|
||||
*-k*, *--ask-pass*::
|
||||
|
||||
Prompt for the connection password, if it is needed for the transport used.
|
||||
For example, using ssh and not having a key-based authentication with ssh-agent.
|
||||
|
||||
*--ask-su-pass*::
|
||||
|
||||
Prompt for su password, used with --su (deprecated, use become).
|
||||
|
||||
*--ask-sudo-pass*::
|
||||
|
||||
Prompt for the password to use with --sudo, if any (deprecated, use become).
|
||||
|
||||
*--ask-vault-pass*::
|
||||
|
||||
Prompt for vault password.
|
||||
|
||||
*-B* 'NUM', *--background=*'NUM'::
|
||||
|
||||
Run commands in the background, killing the task after 'NUM' seconds.
|
||||
|
||||
*--become-method=*'BECOME_METHOD'::
|
||||
|
||||
Privilege escalation method to use (default=sudo),
|
||||
valid choices: [ sudo | su | pbrun | pfexec | doas | dzdo | ksu ]
|
||||
|
||||
*--become-user=*'BECOME_USER'::
|
||||
|
||||
Run operations as this user (default=root).
|
||||
|
||||
*-C*, *--check*::
|
||||
|
||||
Do not make any changes on the remote system, but test resources to see what might
|
||||
have changed. Note this can not scan all possible resource types and is only
|
||||
a simulation.
|
||||
|
||||
*-c* 'CONNECTION', *--connection=*'CONNECTION'::
|
||||
|
||||
Connection type to use. Most common options are 'paramiko' (SSH), 'ssh', 'winrm'
|
||||
and 'local'. 'local' is mostly useful for crontab or kickstarts.
|
||||
|
||||
*-e* 'EXTRA_VARS, *--extra-vars=*'EXTRA_VARS'::
|
||||
|
||||
Extra variables to inject into a playbook, in key=value key=value format or
|
||||
as quoted YAML/JSON (hashes and arrays). To load variables from a file, specify
|
||||
the file preceded by @ (e.g. @vars.yml).
|
||||
|
||||
*-f* 'NUM', *--forks=*'NUM'::
|
||||
|
||||
Level of parallelism. 'NUM' is specified as an integer, the default is 5.
|
||||
|
||||
*-h*, *--help*::
|
||||
*--private-key=*'PRIVATE_KEY_FILE'::
|
||||
|
||||
Show help message and exit.
|
||||
Use this file to authenticate the connection.
|
||||
|
||||
*-i* 'PATH', *--inventory=*'PATH'::
|
||||
|
||||
The 'PATH' to the inventory, which defaults to '/etc/ansible/hosts'.
|
||||
Alternatively you can use a comma separated list of hosts or single host with trailing comma 'host,'.
|
||||
|
||||
*-l* 'SUBSET', *--limit=*'SUBSET'::
|
||||
|
||||
Further limits the selected host/group patterns.
|
||||
You can prefix it with '~' to indicate that the pattern is a regex.
|
||||
|
||||
*--list-hosts*::
|
||||
|
||||
Outputs a list of matching hosts; does not execute anything else.
|
||||
|
||||
*-m* 'NAME', *--module-name=*'NAME'::
|
||||
|
||||
Execute the module called 'NAME'.
|
||||
|
||||
|
||||
*-M* 'DIRECTORY', *--module-path=*'DIRECTORY'::
|
||||
|
||||
The 'DIRECTORY' search path to load modules from. The default is
|
||||
'/usr/share/ansible'. This can also be set with the ANSIBLE_LIBRARY
|
||||
environment variable.
|
||||
|
||||
*-a* \'_ARGUMENTS_', *--args=*\'_ARGUMENTS_'::
|
||||
|
||||
The 'ARGUMENTS' to pass to the module.
|
||||
|
||||
*-k*, *--ask-pass*::
|
||||
|
||||
Prompt for the SSH password instead of assuming key-based authentication with ssh-agent.
|
||||
|
||||
*-K*, *--ask-sudo-pass*::
|
||||
|
||||
Prompt for the password to use with --sudo, if any
|
||||
|
||||
*-o*, *--one-line*::
|
||||
|
||||
Try to output everything on one line.
|
||||
|
||||
*-P* 'NUM', *--poll=*'NUM'::
|
||||
|
||||
Poll a background job every 'NUM' seconds. Requires *-B*.
|
||||
|
||||
*--private-key=*'PRIVATE_KEY_FILE'::
|
||||
|
||||
Use this file to authenticate the connection.
|
||||
|
||||
*-S*, *--su*::
|
||||
|
||||
Run operations with su (deprecated, use become).
|
||||
|
||||
*-R* 'SU_USER', *--se-user=*'SUDO_USER'::
|
||||
|
||||
Run operations with su as this user (default=root) (deprecated, use become).
|
||||
|
||||
*-s*, *--sudo*::
|
||||
|
||||
Run the command as the user given by -u and sudo to root (deprecated, use become).
|
||||
|
||||
*--ssh-common-args=*''-o ProxyCommand="ssh -W %h:%p ..." ...''::
|
||||
|
||||
Add the specified arguments to any sftp/scp/ssh command-line. Useful to
|
||||
set a ProxyCommand to use a jump host, but any arguments that are
|
||||
accepted by all three programs may be specified.
|
||||
|
||||
*--sftp-extra-args=*''-f ...''::
|
||||
|
||||
Add the specified arguments to any sftp command-line.
|
||||
|
||||
*--scp-extra-args=*''-l ...''::
|
||||
|
||||
Add the specified arguments to any scp command-line.
|
||||
|
||||
*--ssh-extra-args=*''-R ...''::
|
||||
|
||||
Add the specified arguments to any ssh command-line.
|
||||
|
||||
*-U* 'SUDO_USERNAME', *--sudo-user=*'SUDO_USERNAME'::
|
||||
|
||||
Sudo to 'SUDO_USERNAME' default is root. (deprecated, use become).
|
||||
Run the command as the user given by -u and sudo to root.
|
||||
|
||||
*-t* 'DIRECTORY', *--tree=*'DIRECTORY'::
|
||||
|
||||
@ -179,51 +93,46 @@ file named after each host.
|
||||
|
||||
Connection timeout to use when trying to talk to hosts, in 'SECONDS'.
|
||||
|
||||
*-B* 'NUM', *--background=*'NUM'::
|
||||
|
||||
Run commands in the background, killing the task after 'NUM' seconds.
|
||||
|
||||
*-P* 'NUM', *--poll=*'NUM'::
|
||||
|
||||
Poll a background job every 'NUM' seconds. Requires *-B*.
|
||||
|
||||
*-u* 'USERNAME', *--user=*'USERNAME'::
|
||||
|
||||
Use this 'USERNAME' to login to the target host, instead of the current user.
|
||||
Use this remote 'USERNAME' instead of the current user.
|
||||
|
||||
*--vault-password-file=*'VAULT_PASSWORD_FILE'::
|
||||
*-U* 'SUDO_USERNAME', *--sudo-user=*'SUDO_USERNAME'::
|
||||
|
||||
A file containing the vault password to be used during the decryption of vault encrypted files.
|
||||
Be sure to keep this file secured if it is used. If the file is executable,
|
||||
it will be run and its standard output will be used as the password.
|
||||
Sudo to 'SUDO_USERNAME' instead of root. Implies --sudo.
|
||||
|
||||
*-v*, *--verbose*::
|
||||
*-c* 'CONNECTION', *--connection=*'CONNECTION'::
|
||||
|
||||
Verbose mode, more output from successful actions will be shown.
|
||||
Give up to three times for more output.
|
||||
Connection type to use. Possible options are 'paramiko' (SSH), 'ssh',
|
||||
and 'local'. 'local' is mostly useful for crontab or kickstarts.
|
||||
|
||||
*--version*::
|
||||
*-l* 'SUBSET', *--limit=*'SUBSET'::
|
||||
|
||||
Show program version number and exit.
|
||||
Further limits the selected host/group patterns.
|
||||
|
||||
*-l* '\~REGEX', *--limit=*'~REGEX'::
|
||||
|
||||
Further limits hosts with a regex pattern.
|
||||
|
||||
INVENTORY
|
||||
---------
|
||||
|
||||
Ansible stores the hosts it can potentially operate on in an inventory.
|
||||
This can be an ini-like file, a script, directory or a list.
|
||||
The ini syntax is one host per line. Groups headers are allowed and
|
||||
are included on their own line, enclosed in square brackets that start the line.
|
||||
Ansible stores the hosts it can potentially operate on in an inventory
|
||||
file. The syntax is one host per line. Groups headers are allowed and
|
||||
are included on their own line, enclosed in square brackets that
|
||||
start the line.
|
||||
|
||||
Ranges of hosts are also supported. For more information and
|
||||
additional options, see the documentation on http://docs.ansible.com/.
|
||||
|
||||
|
||||
ENVIRONMENT
|
||||
-----------
|
||||
|
||||
The following environment variables may be specified.
|
||||
|
||||
ANSIBLE_INVENTORY -- Override the default ansible inventory file
|
||||
|
||||
ANSIBLE_LIBRARY -- Override the default ansible module library path
|
||||
|
||||
ANSIBLE_CONFIG -- Override the default ansible config file
|
||||
|
||||
Many more are available for most options in ansible.cfg
|
||||
|
||||
|
||||
FILES
|
||||
-----
|
||||
|
||||
@ -235,25 +144,37 @@ FILES
|
||||
|
||||
~/.ansible.cfg -- User config file, overrides the default config if present
|
||||
|
||||
ENVIRONMENT
|
||||
-----------
|
||||
|
||||
The following environment variables may be specified.
|
||||
|
||||
ANSIBLE_HOSTS -- Override the default ansible hosts file
|
||||
|
||||
ANSIBLE_LIBRARY -- Override the default ansible module library path
|
||||
|
||||
ANSIBLE_CONFIG -- Override the default ansible config file
|
||||
|
||||
|
||||
AUTHOR
|
||||
------
|
||||
|
||||
Ansible was originally written by Michael DeHaan.
|
||||
See the AUTHORS file for a complete list of contributors.
|
||||
Ansible was originally written by Michael DeHaan. See the AUTHORS file
|
||||
for a complete list of contributors.
|
||||
|
||||
|
||||
COPYRIGHT
|
||||
---------
|
||||
|
||||
Copyright © 2012, Michael DeHaan
|
||||
|
||||
Ansible is released under the terms of the GPLv3 License.
|
||||
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
|
||||
*ansible-playbook*(1), *ansible-pull*(1), *ansible-doc*(1), *ansible-vault*(1), *ansible-galaxy*(1)
|
||||
*ansible-playbook*(1), *ansible-pull*(1), *ansible-doc*(1)
|
||||
|
||||
Extensive documentation is available in the documentation site:
|
||||
<http://docs.ansible.com>. IRC and mailing list info can be found
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
#!/usr/bin/make
|
||||
SITELIB = $(shell python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")
|
||||
FORMATTER=../hacking/module_formatter.py
|
||||
DUMPER=../hacking/dump_playbook_attributes.py
|
||||
|
||||
all: clean docs
|
||||
|
||||
docs: clean directives modules staticmin
|
||||
docs: clean modules staticmin
|
||||
./build-site.py
|
||||
-(cp *.ico htmlout/)
|
||||
-(cp *.jpg htmlout/)
|
||||
@ -21,8 +20,6 @@ viewdocs: clean staticmin
|
||||
htmldocs: staticmin
|
||||
./build-site.py rst
|
||||
|
||||
webdocs: htmldocs
|
||||
|
||||
clean:
|
||||
-rm -rf htmlout
|
||||
-rm -f .buildinfo
|
||||
@ -42,11 +39,8 @@ clean:
|
||||
|
||||
.PHONEY: docs clean
|
||||
|
||||
directives: $(FORMATTER) ../hacking/templates/rst.j2
|
||||
PYTHONPATH=../lib $(DUMPER) --template-dir=../hacking/templates --output-dir=rst/
|
||||
|
||||
modules: $(FORMATTER) ../hacking/templates/rst.j2
|
||||
PYTHONPATH=../lib $(FORMATTER) -t rst --template-dir=../hacking/templates --module-dir=../lib/ansible/modules -o rst/
|
||||
PYTHONPATH=../lib $(FORMATTER) -t rst --template-dir=../hacking/templates --module-dir=../library -o rst/
|
||||
|
||||
staticmin:
|
||||
cat _themes/srtd/static/css/theme.css | sed -e 's/^[ ]*//g; s/[ ]*$$//g; s/\([:{;,]\) /\1/g; s/ {/{/g; s/\/\*.*\*\///g; /^$$/d' | sed -e :a -e '$$!N; s/\n\(.\)/\1/; ta' > _themes/srtd/static/css/theme.min.css
|
||||
cat _themes/srtd/static/css/theme.css | sed -e 's/^[ \t]*//g; s/[ \t]*$$//g; s/\([:{;,]\) /\1/g; s/ {/{/g; s/\/\*.*\*\///g; /^$$/d' | sed -e :a -e '$$!N; s/\n\(.\)/\1/; ta' > _themes/srtd/static/css/theme.min.css
|
||||
|
||||
@ -4,7 +4,7 @@ Homepage and documentation source for Ansible
|
||||
This project hosts the source behind [docs.ansible.com](http://docs.ansible.com/)
|
||||
|
||||
Contributions to the documentation are welcome. To make changes, submit a pull request
|
||||
that changes the reStructuredText files in the "rst/" directory only, and the core team can
|
||||
that changes the reStructuredText files in the "rst/" directory only, and Michael can
|
||||
do a docs build and push the static files.
|
||||
|
||||
If you wish to verify output from the markup
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
* helper function to return a node containing the
|
||||
* search summary for a given text. keywords is a list
|
||||
* of stemmed words, hlwords is the list of normal, unstemmed
|
||||
* words. the first one is used to find the occurrence, the
|
||||
* words. the first one is used to find the occurance, the
|
||||
* latter for highlighting it.
|
||||
*/
|
||||
|
||||
@ -557,4 +557,4 @@ var Search = {
|
||||
|
||||
$(document).ready(function() {
|
||||
Search.init();
|
||||
});
|
||||
});
|
||||
@ -12,24 +12,14 @@
|
||||
|
||||
<hr/>
|
||||
|
||||
<script type="text/javascript">
|
||||
(function(w,d,t,u,n,s,e){w['SwiftypeObject']=n;w[n]=w[n]||function(){
|
||||
(w[n].q=w[n].q||[]).push(arguments);};s=d.createElement(t);
|
||||
e=d.getElementsByTagName(t)[0];s.async=1;s.src=u;e.parentNode.insertBefore(s,e);
|
||||
})(window,document,'script','//s.swiftypecdn.com/install/v2/st.js','_st');
|
||||
|
||||
_st('install','yABGvz2N8PwcwBxyfzUc','2.0.0');
|
||||
</script>
|
||||
|
||||
<p>
|
||||
Copyright © 2016 Red Hat, Inc.
|
||||
<br>
|
||||
© Copyright 2014 <a href="http://ansible.com">Ansible, Inc.</a>.
|
||||
|
||||
{%- if last_updated %}
|
||||
{% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
|
||||
{%- endif %}
|
||||
</p>
|
||||
<p>
|
||||
Ansible docs are generated from <a href="https://github.com/ansible/ansible">GitHub sources</A> using <A HREF="http://sphinx-doc.org/">Sphinx</A> using a theme provided by <a href="http://readthedocs.org">Read the Docs</a>. {% if pagename.endswith("_module") %}. Module documentation is not edited directly, but is generated from the source code for the modules. To submit an update to module docs, edit the 'DOCUMENTATION' metadata in the <A HREF="https://github.com/ansible/ansible-modules-core">core</A> and <A HREF="https://github.com/ansible/ansible-modules-extras">extras</A> modules source repositories. {% endif %}
|
||||
</p>
|
||||
|
||||
Ansible docs are generated from <a href="http://github.com/ansible/ansible">GitHub sources</A> using <A HREF="http://sphinx-doc.org/">Sphinx</A> using a theme provided by <a href="http://readthedocs.org">Read the Docs</a>. {% if pagename.endswith("_module") %}. Module documentation is not edited directly, but is generated from the source code for the modules. To submit an update to module docs, edit the 'DOCUMENTATION' metadata in the <A HREF="https://github.com/ansible/ansible/tree/devel/library">module source tree</A>. {% endif %}
|
||||
|
||||
</footer>
|
||||
|
||||
@ -113,9 +113,6 @@
|
||||
}
|
||||
</style>
|
||||
|
||||
<link href='https://fonts.googleapis.com/css?family=Open+Sans:400,300'
|
||||
rel='stylesheet' type='text/css'>
|
||||
|
||||
</head>
|
||||
|
||||
<body class="wy-body-for-nav">
|
||||
@ -125,29 +122,18 @@
|
||||
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='//www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); })(window,document,'script','dataLayer','GTM-PSB293');</script>
|
||||
<!-- End Google Tag Manager -->
|
||||
|
||||
<a class="DocSite-nav" href="http://docs.ansible.com/">
|
||||
<img class="DocSiteNav-logo"
|
||||
src="{{ pathto('_static/', 1) }}images/logo_invert.png"
|
||||
alt="Ansible Logo">
|
||||
<div class="DocSiteNav-title">
|
||||
Documentation
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div class="wy-grid-for-nav">
|
||||
|
||||
{# SIDE NAV, TOGGLES ON MOBILE #}
|
||||
<nav data-toggle="wy-nav-shift" class="wy-nav-side">
|
||||
|
||||
<div style="background-color:#5bbdbf;height=80px;margin:'auto auto auto auto'">
|
||||
<a class="DocSiteProduct-header DocSiteProduct-header--core"
|
||||
href="http://docs.ansible.com/ansible/">
|
||||
<div class="DocSiteProduct-productName">
|
||||
<div class="DocSiteProduct-logoText">
|
||||
ANSIBLE
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div style='background-color:#64C5C7'>
|
||||
<br/>
|
||||
<center><a href="{{ pathto(master_doc) }}"><img src="http://cdn2.hubspot.net/hub/330046/file-445792629-png/ANSI_logotype_web_white.png" style="border-radius:0;background-color:#64C5C7;width:214px;height:26px;margin:'auto auto auto auto'"></a></center>
|
||||
<br/>
|
||||
</div>
|
||||
<div class="wy-side-nav-search" style="background-color:#64C5C7;height=80px;margin:'auto auto auto auto'">
|
||||
<!-- <a href="{{ pathto(master_doc) }}" class="icon icon-home"> {{ project }}</a> -->
|
||||
{% include "searchbox.html" %}
|
||||
</div>
|
||||
|
||||
<div id="menu-id" class="wy-menu wy-menu-vertical" data-spy="affix">
|
||||
@ -161,9 +147,16 @@
|
||||
<!-- changeable widget -->
|
||||
<center>
|
||||
<br/>
|
||||
<a href="http://www.ansible.com/docs-left?utm_source=docs">
|
||||
<img style="border-width:0px;" src="https://cdn2.hubspot.net/hubfs/330046/docs-graphics/ASB-docs-left-rail.png" />
|
||||
</a>
|
||||
<span class="hs-cta-wrapper" id="hs-cta-wrapper-71d47584-8ef5-4b06-87ae-8d25bc2a837e">
|
||||
<span class="hs-cta-node hs-cta-71d47584-8ef5-4b06-87ae-8d25bc2a837e" id="hs-cta-71d47584-8ef5-4b06-87ae-8d25bc2a837e">
|
||||
<!--[if lte IE 8]><div id="hs-cta-ie-element"></div><![endif]-->
|
||||
<a href="http://cta-redirect.hubspot.com/cta/redirect/330046/71d47584-8ef5-4b06-87ae-8d25bc2a837e"><img class="hs-cta-img" id="hs-cta-img-71d47584-8ef5-4b06-87ae-8d25bc2a837e" style="border-width:0px;" src="https://no-cache.hubspot.com/cta/default/330046/71d47584-8ef5-4b06-87ae-8d25bc2a837e.png" /></a>
|
||||
</span>
|
||||
<script charset="utf-8" src="https://js.hscta.net/cta/current.js"></script>
|
||||
<script type="text/javascript">
|
||||
hbspt.cta.load(330046, '71d47584-8ef5-4b06-87ae-8d25bc2a837e');
|
||||
</script>
|
||||
</span>
|
||||
</center>
|
||||
|
||||
|
||||
@ -183,19 +176,6 @@
|
||||
{# PAGE CONTENT #}
|
||||
<div class="wy-nav-content">
|
||||
<div class="rst-content">
|
||||
|
||||
<!-- Banner ads -->
|
||||
<div class="DocSiteBanner">
|
||||
<a class="DocSiteBanner-imgWrapper"
|
||||
href="http://www.ansible.com/docs-top?utm_source=docs">
|
||||
<img src="https://cdn2.hubspot.net/hubfs/330046/docs-graphics/ASB-docs-top-left.png">
|
||||
</a>
|
||||
<a class="DocSiteBanner-imgWrapper"
|
||||
href="http://www.ansible.com/docs-top?utm_source=docs">
|
||||
<img src="https://cdn2.hubspot.net/hubfs/330046/docs-graphics/ASB-docs-top-right.png">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% include "breadcrumbs.html" %}
|
||||
<div id="page-content">
|
||||
{% block body %}{% endblock %}
|
||||
|
||||
205
docsite/_themes/srtd/layout_old.html
Normal file
205
docsite/_themes/srtd/layout_old.html
Normal file
@ -0,0 +1,205 @@
|
||||
{#
|
||||
basic/layout.html
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Master layout template for Sphinx themes.
|
||||
|
||||
:copyright: Copyright 2007-2013 by the Sphinx team, see AUTHORS.
|
||||
:license: BSD, see LICENSE for details.
|
||||
#}
|
||||
{%- block doctype -%}
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
{%- endblock %}
|
||||
{%- set reldelim1 = reldelim1 is not defined and ' »' or reldelim1 %}
|
||||
{%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %}
|
||||
{%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and
|
||||
(sidebars != []) %}
|
||||
{%- set url_root = pathto('', 1) %}
|
||||
{# XXX necessary? #}
|
||||
{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
|
||||
{%- if not embedded and docstitle %}
|
||||
{%- set titlesuffix = " — "|safe + docstitle|e %}
|
||||
{%- else %}
|
||||
{%- set titlesuffix = "" %}
|
||||
{%- endif %}
|
||||
|
||||
{%- macro relbar() %}
|
||||
<div class="related">
|
||||
<h3>{{ _('Navigation') }}</h3>
|
||||
<ul>
|
||||
{%- for rellink in rellinks %}
|
||||
<li class="right" {% if loop.first %}style="margin-right: 10px"{% endif %}>
|
||||
<a href="{{ pathto(rellink[0]) }}" title="{{ rellink[1]|striptags|e }}"
|
||||
{{ accesskey(rellink[2]) }}>{{ rellink[3] }}</a>
|
||||
{%- if not loop.first %}{{ reldelim2 }}{% endif %}</li>
|
||||
{%- endfor %}
|
||||
{%- block rootrellink %}
|
||||
<li><a href="{{ pathto(master_doc) }}">{{ shorttitle|e }}</a>{{ reldelim1 }}</li>
|
||||
{%- endblock %}
|
||||
{%- for parent in parents %}
|
||||
<li><a href="{{ parent.link|e }}" {% if loop.last %}{{ accesskey("U") }}{% endif %}>{{ parent.title }}</a>{{ reldelim1 }}</li>
|
||||
{%- endfor %}
|
||||
{%- block relbaritems %} {% endblock %}
|
||||
</ul>
|
||||
</div>
|
||||
{%- endmacro %}
|
||||
|
||||
{%- macro sidebar() %}
|
||||
{%- if render_sidebar %}
|
||||
<div class="sphinxsidebar">
|
||||
<div class="sphinxsidebarwrapper">
|
||||
{%- block sidebarlogo %}
|
||||
{%- if logo %}
|
||||
<p class="logo"><a href="{{ pathto(master_doc) }}">
|
||||
<img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
|
||||
</a></p>
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
{%- if sidebars != None %}
|
||||
{#- new style sidebar: explicitly include/exclude templates #}
|
||||
{%- for sidebartemplate in sidebars %}
|
||||
{%- include sidebartemplate %}
|
||||
{%- endfor %}
|
||||
{%- else %}
|
||||
{#- old style sidebars: using blocks -- should be deprecated #}
|
||||
{%- block sidebartoc %}
|
||||
{%- include "localtoc.html" %}
|
||||
{%- endblock %}
|
||||
{%- block sidebarrel %}
|
||||
{%- include "relations.html" %}
|
||||
{%- endblock %}
|
||||
{%- block sidebarsourcelink %}
|
||||
{%- include "sourcelink.html" %}
|
||||
{%- endblock %}
|
||||
{%- if customsidebar %}
|
||||
{%- include customsidebar %}
|
||||
{%- endif %}
|
||||
{%- block sidebarsearch %}
|
||||
{%- include "searchbox.html" %}
|
||||
{%- endblock %}
|
||||
{%- endif %}
|
||||
</div>
|
||||
</div>
|
||||
{%- endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{%- macro script() %}
|
||||
<script type="text/javascript">
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: '{{ url_root }}',
|
||||
VERSION: '{{ release|e }}',
|
||||
COLLAPSE_INDEX: false,
|
||||
FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}',
|
||||
HAS_SOURCE: {{ has_source|lower }}
|
||||
};
|
||||
</script>
|
||||
{%- for scriptfile in script_files %}
|
||||
<script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
|
||||
{%- endfor %}
|
||||
{%- endmacro %}
|
||||
|
||||
{%- macro css() %}
|
||||
<link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
|
||||
<link rel="stylesheet" href="{{ pathto('_static/pygments.css', 1) }}" type="text/css" />
|
||||
{%- for cssfile in css_files %}
|
||||
<link rel="stylesheet" href="{{ pathto(cssfile, 1) }}" type="text/css" />
|
||||
{%- endfor %}
|
||||
{%- endmacro %}
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset={{ encoding }}" />
|
||||
{{ metatags }}
|
||||
{%- block htmltitle %}
|
||||
<title>{{ title|striptags|e }}{{ titlesuffix }}</title>
|
||||
{%- endblock %}
|
||||
{{ css() }}
|
||||
{%- if not embedded %}
|
||||
{{ script() }}
|
||||
{%- if use_opensearch %}
|
||||
<link rel="search" type="application/opensearchdescription+xml"
|
||||
title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}"
|
||||
href="{{ pathto('_static/opensearch.xml', 1) }}"/>
|
||||
{%- endif %}
|
||||
{%- if favicon %}
|
||||
<link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
{%- block linktags %}
|
||||
{%- if hasdoc('about') %}
|
||||
<link rel="author" title="{{ _('About these documents') }}" href="{{ pathto('about') }}" />
|
||||
{%- endif %}
|
||||
{%- if hasdoc('genindex') %}
|
||||
<link rel="index" title="{{ _('Index') }}" href="{{ pathto('genindex') }}" />
|
||||
{%- endif %}
|
||||
{%- if hasdoc('search') %}
|
||||
<link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}" />
|
||||
{%- endif %}
|
||||
{%- if hasdoc('copyright') %}
|
||||
<link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}" />
|
||||
{%- endif %}
|
||||
<link rel="top" title="{{ docstitle|e }}" href="{{ pathto('index') }}" />
|
||||
{%- if parents %}
|
||||
<link rel="up" title="{{ parents[-1].title|striptags|e }}" href="{{ parents[-1].link|e }}" />
|
||||
{%- endif %}
|
||||
{%- if next %}
|
||||
<link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}" />
|
||||
{%- endif %}
|
||||
{%- if prev %}
|
||||
<link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}" />
|
||||
{%- endif %}
|
||||
{%- endblock %}
|
||||
{%- block extrahead %} {% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
{%- block header %}{% endblock %}
|
||||
|
||||
{%- block relbar1 %}{{ relbar() }}{% endblock %}
|
||||
|
||||
{%- block content %}
|
||||
{%- block sidebar1 %} {# possible location for sidebar #} {% endblock %}
|
||||
|
||||
<div class="document">
|
||||
{%- block document %}
|
||||
<div class="documentwrapper">
|
||||
{%- if render_sidebar %}
|
||||
<div class="bodywrapper">
|
||||
{%- endif %}
|
||||
<div class="body">
|
||||
{% block body %} {% endblock %}
|
||||
</div>
|
||||
{%- if render_sidebar %}
|
||||
</div>
|
||||
{%- endif %}
|
||||
</div>
|
||||
{%- endblock %}
|
||||
|
||||
{%- block sidebar2 %}{{ sidebar() }}{% endblock %}
|
||||
<div class="clearer"></div>
|
||||
</div>
|
||||
{%- endblock %}
|
||||
|
||||
{%- block relbar2 %}{{ relbar() }}{% endblock %}
|
||||
|
||||
{%- block footer %}
|
||||
<div class="footer">
|
||||
{%- if show_copyright %}
|
||||
{%- if hasdoc('copyright') %}
|
||||
{% trans path=pathto('copyright'), copyright=copyright|e %}© <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %}
|
||||
{%- else %}
|
||||
{% trans copyright=copyright|e %}© Copyright {{ copyright }}.{% endtrans %}
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
{%- if last_updated %}
|
||||
{% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
|
||||
{%- endif %}
|
||||
{%- if show_sphinx %}
|
||||
{% trans sphinx_version=sphinx_version|e %}Created using <a href="http://sphinx-doc.org/">Sphinx</a> {{ sphinx_version }}.{% endtrans %}
|
||||
{%- endif %}
|
||||
</div>
|
||||
<p>asdf asdf asdf asdf 22</p>
|
||||
{%- endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user