Compare commits
69 Commits
stable-2.0
...
release1.6
| Author | SHA1 | Date | |
|---|---|---|---|
| cb4b196288 | |||
| 8ed484c544 | |||
| cc4129f6a1 | |||
| c35bbdaa21 | |||
| 84c40fc111 | |||
| 2c6c1b99c7 | |||
| 2aa7ce006f | |||
| b8b96b2109 | |||
| dffea7f877 | |||
| 7059528059 | |||
| fadf02d0c6 | |||
| 3ebecdd1ed | |||
| 58a0bcbe51 | |||
| 10074d3622 | |||
| 2625196f5d | |||
| e85a9abe75 | |||
| b99ea2723a | |||
| 7cb7fdc2a7 | |||
| 936e1de18b | |||
| c4b6c0ab84 | |||
| 4dd5eeb1c4 | |||
| d8c8c0823a | |||
| 3801be693b | |||
| 75adb3978b | |||
| 4e4ffa669a | |||
| ce80c0b8af | |||
| b50a64b0a1 | |||
| adfbcb8760 | |||
| 5128bced6b | |||
| 549f67c0d8 | |||
| d98b6ae8ab | |||
| ce4883bcc5 | |||
| a75a84b44a | |||
| f132e99b2a | |||
| 4d0ce5bd41 | |||
| b01da1b780 | |||
| 1248d2ce8a | |||
| d72cd70cc8 | |||
| 362626d07c | |||
| a54b8764fa | |||
| 6b4584d004 | |||
| cb0842ab49 | |||
| 2432b45eb1 | |||
| d6214c5d56 | |||
| c5b6d23c63 | |||
| e83f14200c | |||
| 7c0431b1e4 | |||
| 1c5055a401 | |||
| 24d885e368 | |||
| 8851ceaa9e | |||
| 854bf14222 | |||
| d8526a6c80 | |||
| e0fe6ca5d8 | |||
| ca2a12aa5b | |||
| 29018cf2a7 | |||
| 9d8eee633d | |||
| 8cc71a6eec | |||
| 3565249471 | |||
| 595813a1fe | |||
| 0de306574e | |||
| d33486d52a | |||
| 0dabb37337 | |||
| 9a930fb481 | |||
| d4e11c5c69 | |||
| 08f4984d92 | |||
| 7daaabdedb | |||
| 9943c50863 | |||
| ce15813e70 | |||
| f05d457560 |
@ -1,4 +0,0 @@
|
||||
[report]
|
||||
omit =
|
||||
*/python?.?/*
|
||||
*/site-packages/nose/*
|
||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -1 +0,0 @@
|
||||
CHANGELOG.md merge=union
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@ -19,12 +19,6 @@ 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
|
||||
@ -47,10 +41,7 @@ deb-build
|
||||
*.swo
|
||||
credentials.yml
|
||||
# test output
|
||||
*.retry
|
||||
*.out
|
||||
.coverage
|
||||
.tox
|
||||
results.xml
|
||||
coverage.xml
|
||||
/test/units/cover-html
|
||||
|
||||
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
|
||||
42
.travis.yml
42
.travis.yml
@ -1,42 +0,0 @@
|
||||
dist: trusty
|
||||
sudo: required
|
||||
services:
|
||||
- docker
|
||||
language: python
|
||||
matrix:
|
||||
include:
|
||||
- env: TARGET=sanity TOXENV=py24
|
||||
- env: TARGET=sanity TOXENV=py26
|
||||
python: 2.6
|
||||
- env: TARGET=sanity TOXENV=py27
|
||||
python: 2.7
|
||||
- env: TARGET=sanity TOXENV=py34
|
||||
python: 3.4
|
||||
- env: TARGET=sanity TOXENV=py35
|
||||
python: 3.5
|
||||
- env: TARGET=centos6
|
||||
- env: TARGET=centos7 TARGET_OPTIONS="--volume=/sys/fs/cgroup:/sys/fs/cgroup:ro"
|
||||
- env: TARGET=fedora23 TARGET_OPTIONS="--volume=/sys/fs/cgroup:/sys/fs/cgroup:ro"
|
||||
- env: TARGET=fedora-rawhide TARGET_OPTIONS="--volume=/sys/fs/cgroup:/sys/fs/cgroup:ro"
|
||||
- env: TARGET=ubuntu1204
|
||||
- env: TARGET=ubuntu1404
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- deadsnakes
|
||||
packages:
|
||||
- python2.4
|
||||
install:
|
||||
- pip install tox coveralls
|
||||
script:
|
||||
- ./test/utils/run_tests.sh
|
||||
after_success:
|
||||
- coveralls
|
||||
notifications:
|
||||
irc:
|
||||
channels:
|
||||
- "chat.freenode.net#ansible-notices"
|
||||
on_success: change
|
||||
on_failure: always
|
||||
skip_join: true
|
||||
nick: ansibletravis
|
||||
912
CHANGELOG.md
912
CHANGELOG.md
@ -1,889 +1,13 @@
|
||||
Ansible Changes By Release
|
||||
==========================
|
||||
|
||||
## 2.1 TBD - ACTIVE DEVELOPMENT
|
||||
## 1.6.5 "And the Cradle Will Rock" - Jun 25, 2014
|
||||
|
||||
####New Modules:
|
||||
* cloudstack: cs_volume
|
||||
- Additional tweaks to prevent the incorrect execution of untrusted data
|
||||
|
||||
####New Filters:
|
||||
* extract
|
||||
## 1.6.4 "And the Cradle Will Rock" - Jun 25, 2014
|
||||
|
||||
## 2.0.3 "Over the Hills and Far Away"
|
||||
|
||||
* Backport fix to uri module to return the body of an error response
|
||||
* Backport fix to uri module to handle file:/// uris.
|
||||
* Backport fix to uri module to fix traceback when handling certain server error types.
|
||||
|
||||
## 2.0.2 "Over the Hills and Far Away"
|
||||
|
||||
* Backport of the 2.1 feature to ensure per-item callbacks are sent as they occur,
|
||||
rather than all at once at the end of the task.
|
||||
* Fixed bugs related to the iteration of tasks when certain combinations of roles,
|
||||
blocks, and includes were used, especially when handling errors in rescue/always
|
||||
portions of blocks.
|
||||
* Fixed handling of redirects in our helper code, and ported the uri module to use
|
||||
this helper code. This removes the httplib dependency for this module while fixing
|
||||
some bugs related to redirects and SSL certs.
|
||||
* Fixed some bugs related to the incorrect creation of extra temp directories for
|
||||
uploading files, which were not cleaned up properly.
|
||||
* Improved error reporting in certain situations, to provide more information such as
|
||||
the playbook file/line.
|
||||
* Fixed a bug related to the variable precedence of role parameters, especially when
|
||||
a role may be used both as a dependency of a role and directly by itself within the
|
||||
same play.
|
||||
* Fixed some bugs in the 2.0 implementation of do/until.
|
||||
* Fixed some bugs related to run_once:
|
||||
- Ensure that all hosts are marked as failed if a task marked as run_once fails.
|
||||
- Show a warning when using the free strategy when a run_once task is encountered, as
|
||||
there is no way for the free strategy to guarantee the task is not run more than once.
|
||||
* Fixed a bug where the assemble module was not honoring check mode in some situations.
|
||||
* Fixed a bug related to delegate_to, where we were incorrectly using variables from
|
||||
the inventory host rather than the delegated-to host.
|
||||
* The 'package' meta-module now properly squashes items down to a single execution (as the
|
||||
apt/yum/other package modules do).
|
||||
* Fixed a bug related to the ansible-galaxy CLI command dealing with paged results from
|
||||
the Galaxy server.
|
||||
* Pipelining support is now available for the local and jail connection plugins, which is
|
||||
useful for users who do not wish to have temp files/directories created when running
|
||||
tasks with these connection types.
|
||||
* Improvements in support for additional shell types.
|
||||
* Improvements in the code which is used to calculate checksums for remote files.
|
||||
* Some speed ups and bug fixes related to the variable merging code.
|
||||
* Workaround bug in python subprocess on El Capitan that was making vault fail
|
||||
when attempting to encrypt a file
|
||||
* Fix lxc_container module having predictable temp file names and setting file
|
||||
permissions on the temporary file too leniently on a temporary file that was
|
||||
executed as a script. Addresses CVE-2016-3096
|
||||
* Fix a bug in the uri module where setting headers via module params that
|
||||
start with HEADER_ were causing a traceback.
|
||||
* Fix bug in the free strategy that was causing it to synchronize its workers
|
||||
after every task (making it a lot more like linear than it should have been).
|
||||
|
||||
## 2.0.1 "Over the Hills and Far Away"
|
||||
|
||||
* Fixes a major compatibility break in the synchronize module shipped with
|
||||
2.0.0.x. That version of synchronize ran sudo on the controller prior to
|
||||
running rsync. In 1.9.x and previous, sudo was run on the host that rsync
|
||||
connected to. 2.0.1 restores the 1.9.x behaviour.
|
||||
* Additionally, several other problems with where synchronize chose to run when
|
||||
combined with delegate_to were fixed. In particular, if a playbook targetted
|
||||
localhost and then delegated_to a remote host the prior behavior (in 1.9.x
|
||||
and 2.0.0.x) was to copy files between the src and destination directories on
|
||||
the delegated host. This has now been fixed to copy between localhost and
|
||||
the delegated host.
|
||||
* Fix a regression where synchronize was unable to deal with unicode paths.
|
||||
* Fix a regression where synchronize deals with inventory hosts that use
|
||||
localhost but with an alternate port.
|
||||
* Fixes a regression where the retry files feature was not implemented.
|
||||
* Fixes a regression where the any_errors_fatal option was implemented in 2.0
|
||||
incorrectly, and also adds a feature where any_errors_fatal can be set at
|
||||
the block level.
|
||||
* Fix tracebacks when playbooks or ansible itself were located in directories
|
||||
with unicode characters.
|
||||
* Fix bug when sending unicode characters to an external pager for display.
|
||||
* Fix a bug with squashing loops for special modules (mostly package managers).
|
||||
The optimization was squashing when the loop did not apply to the selection
|
||||
of packages. This has now been fixed.
|
||||
* Temp files created when using vault are now "shredded" using the unix shred
|
||||
program which overwrites the file with random data.
|
||||
* Some fixes to cloudstack modules for case sensitivity
|
||||
* Fix non-newstyle modules (non-python modules and old-style modules) to
|
||||
disabled pipelining.
|
||||
* Fix fetch module failing even if fail_on_missing is set to False
|
||||
* Fix for cornercase when local connections, sudo, and raw were used together.
|
||||
* Fix dnf module to remove dependent packages when state=absent is specified.
|
||||
This was a feature of the 1.9.x version that was left out by mistake when the
|
||||
module was rewritten for 2.0.
|
||||
* Fix bugs with non-english locales in yum, git, and apt modules
|
||||
* Fix a bug with the dnf module where state=latest could only upgrade, not install.
|
||||
* Fix to make implicit fact gathering task correctly inherit settings from play,
|
||||
this might cause an error if settings environment on play depending on 'ansible_env'
|
||||
which was previouslly ignored
|
||||
|
||||
## 2.0 "Over the Hills and Far Away" - Jan 12, 2016
|
||||
|
||||
###Major Changes:
|
||||
|
||||
* Releases are now named after Led Zeppelin songs, 1.9 will be the last Van Halen named release.
|
||||
* The new block/rescue/always directives allow for making task blocks and exception-like semantics
|
||||
* New strategy plugins (e.g. `free`) allow control over the flow of task execution per play. The default (`linear`) will be the same as before.
|
||||
* Improved error handling, with more detailed parser messages. General exception handling and display has been revamped.
|
||||
* Task includes are now evaluated during execution, allowing more dynamic includes and options. Play includes are unchanged both still use the `include` directive.
|
||||
* "with\_<lookup>" loops can now be used with task includes since they are dynamic.
|
||||
* Callback, connection, cache and lookup plugin APIs have changed. Existing plugins might require modification to work with the new versions.
|
||||
* Callbacks are now shipped in the active directory and don't need to be copied, just whitelisted in ansible.cfg.
|
||||
* Many API changes. Those integrating directly with Ansible's API will encounter breaking changes, but the new API is much easier to use and test.
|
||||
* Settings are now more inheritable; what you set at play, block or role will be automatically inherited by the contained tasks.
|
||||
This allows for new features to automatically be settable at all levels, previously we had to manually code this.
|
||||
* Vars are now settable at play, block, role and task level with the `vars` directive and scoped to the tasks contained.
|
||||
* Template code now retains types for bools and numbers instead of turning them into strings.
|
||||
If you need the old behaviour, quote the value and it will get passed around as a string
|
||||
* Empty variables and variables set to null in yaml will no longer be converted to empty strings. They will retain the value of `None`.
|
||||
To go back to the old behaviour, you can override the `null_representation` setting to an empty string in your config file or
|
||||
by setting the `ANSIBLE_NULL_REPRESENTATION` environment variable.
|
||||
* Added `meta: refresh_inventory` to force rereading the inventory in a play.
|
||||
This re-executes inventory scripts, but does not force them to ignore any cache they might use.
|
||||
* New delegate_facts directive, a boolean that allows you to apply facts to the delegated host (true/yes) instead of the inventory_hostname (no/false) which is the default and previous behaviour.
|
||||
* local connections now work with 'su' as a privilege escalation method
|
||||
* Ansible 2.0 has deprecated the “ssh” from ansible_ssh_user, ansible_ssh_host, and ansible_ssh_port to become ansible_user, ansible_host, and ansible_port.
|
||||
* New ssh configuration variables(`ansible_ssh_common_args`, `ansible_ssh_extra_args`) can be used to configure a
|
||||
per-group or per-host ssh ProxyCommand or set any other ssh options.
|
||||
`ansible_ssh_extra_args` is used to set options that are accepted only by ssh (not sftp or scp, which have their own analogous settings).
|
||||
* ansible-pull can now verify the code it runs when using git as a source repository, using git's code signing and verification features.
|
||||
* Backslashes used when specifying parameters in jinja2 expressions in YAML dicts sometimes needed to be escaped twice.
|
||||
This has been fixed so that escaping once works. Here's an example of how playbooks need to be modified:
|
||||
|
||||
```
|
||||
# Syntax in 1.9.x
|
||||
- debug:
|
||||
msg: "{{ 'test1_junk 1\\\\3' | regex_replace('(.*)_junk (.*)', '\\\\1 \\\\2') }}"
|
||||
# Syntax in 2.0.x
|
||||
- debug:
|
||||
msg: "{{ 'test1_junk 1\\3' | regex_replace('(.*)_junk (.*)', '\\1 \\2') }}"
|
||||
|
||||
# Output:
|
||||
"msg": "test1 1\\3"
|
||||
```
|
||||
|
||||
* When a string with a trailing newline was specified in the playbook via yaml
|
||||
dict format, the trailing newline was stripped. When specified in key=value
|
||||
format the trailing newlines were kept. In v2, both methods of specifying the
|
||||
string will keep the trailing newlines. If you relied on the trailing
|
||||
newline being stripped you can change your playbook like this:
|
||||
|
||||
```
|
||||
# Syntax in 1.9.2
|
||||
vars:
|
||||
message: >
|
||||
Testing
|
||||
some things
|
||||
tasks:
|
||||
- debug:
|
||||
msg: "{{ message }}"
|
||||
|
||||
# Syntax in 2.0.x
|
||||
vars:
|
||||
old_message: >
|
||||
Testing
|
||||
some things
|
||||
message: "{{ old_messsage[:-1] }}"
|
||||
- debug:
|
||||
msg: "{{ message }}"
|
||||
# Output
|
||||
"msg": "Testing some things"
|
||||
```
|
||||
|
||||
* In 1.9.x, newlines in templates were converted to Unix EOL conventions. If
|
||||
someone wanted a templated file to end up with Windows or Mac EOL
|
||||
conventions, this could cause problems for them. In 2.x newlines now remain
|
||||
as specified in the template file.
|
||||
|
||||
* When specifying complex args as a variable, the variable must use the full jinja2
|
||||
variable syntax ('{{var_name}}') - bare variable names there are no longer accepted.
|
||||
In fact, even specifying args with variables has been deprecated, and will not be
|
||||
allowed in future versions:
|
||||
|
||||
```
|
||||
---
|
||||
- hosts: localhost
|
||||
connection: local
|
||||
gather_facts: false
|
||||
vars:
|
||||
my_dirs:
|
||||
- { path: /tmp/3a, state: directory, mode: 0755 }
|
||||
- { path: /tmp/3b, state: directory, mode: 0700 }
|
||||
tasks:
|
||||
- file:
|
||||
args: "{{item}}"
|
||||
with_items: my_dirs
|
||||
```
|
||||
|
||||
* The bigip\* networking modules have a new parameter, validate_certs. When
|
||||
True (the default) the module will validate any hosts it connects to against
|
||||
the TLS certificates it presents when run on new enough python versions. If
|
||||
the python version is too old to validate certificates or you used certificates
|
||||
that cannot be validated against available CAs you will need to add
|
||||
validate_certs=no to your playbook for those tasks.
|
||||
|
||||
###Plugins
|
||||
|
||||
* Rewritten dnf module that should be faster and less prone to encountering bugs in cornercases
|
||||
* WinRM connection plugin passes all vars named `ansible_winrm_*` to the underlying pywinrm client. This allows, for instance, `ansible_winrm_server_cert_validation=ignore` to be used with newer versions of pywinrm to disable certificate validation on Python 2.7.9+.
|
||||
* WinRM connection plugin put_file is significantly faster and no longer has file size limitations.
|
||||
|
||||
####Deprecated Modules (new ones in parens):
|
||||
|
||||
* ec2_ami_search (ec2_ami_find)
|
||||
* quantum_network (os_network)
|
||||
* glance_image
|
||||
* nova_compute (os_server)
|
||||
* quantum_floating_ip (os_floating_ip)
|
||||
* quantum_router (os_router)
|
||||
* quantum_router_gateway (os_router)
|
||||
* quantum_router_interface (os_router)
|
||||
|
||||
####New Modules:
|
||||
|
||||
* amazon: ec2_ami_copy
|
||||
* amazon: ec2_ami_find
|
||||
* amazon: ec2_elb_facts
|
||||
* amazon: ec2_eni
|
||||
* amazon: ec2_eni_facts
|
||||
* amazon: ec2_remote_facts
|
||||
* amazon: ec2_vpc_igw
|
||||
* amazon: ec2_vpc_net
|
||||
* amazon: ec2_vpc_net_facts
|
||||
* amazon: ec2_vpc_route_table
|
||||
* amazon: ec2_vpc_route_table_facts
|
||||
* amazon: ec2_vpc_subnet
|
||||
* amazon: ec2_vpc_subnet_facts
|
||||
* amazon: ec2_win_password
|
||||
* amazon: ecs_cluster
|
||||
* amazon: ecs_task
|
||||
* amazon: ecs_taskdefinition
|
||||
* amazon: elasticache_subnet_group_facts
|
||||
* amazon: iam
|
||||
* amazon: iam_cert
|
||||
* amazon: iam_policy
|
||||
* amazon: route53_facts
|
||||
* amazon: route53_health_check
|
||||
* amazon: route53_zone
|
||||
* amazon: sts_assume_role
|
||||
* amazon: s3_bucket
|
||||
* amazon: s3_lifecycle
|
||||
* amazon: s3_logging
|
||||
* amazon: sqs_queue
|
||||
* amazon: sns_topic
|
||||
* amazon: sts_assume_role
|
||||
* apk
|
||||
* bigip_gtm_wide_ip
|
||||
* bundler
|
||||
* centurylink: clc_aa_policy
|
||||
* centurylink: clc_alert_policy
|
||||
* centurylink: clc_blueprint_package
|
||||
* centurylink: clc_firewall_policy
|
||||
* centurylink: clc_group
|
||||
* centurylink: clc_loadbalancer
|
||||
* centurylink: clc_modify_server
|
||||
* centurylink: clc_publicip
|
||||
* centurylink: clc_server
|
||||
* centurylink: clc_server_snapshot
|
||||
* circonus_annotation
|
||||
* consul
|
||||
* consul_acl
|
||||
* consul_kv
|
||||
* consul_session
|
||||
* cloudtrail
|
||||
* cloudstack: cs_account
|
||||
* cloudstack: cs_affinitygroup
|
||||
* cloudstack: cs_domain
|
||||
* cloudstack: cs_facts
|
||||
* cloudstack: cs_firewall
|
||||
* cloudstack: cs_iso
|
||||
* cloudstack: cs_instance
|
||||
* cloudstack: cs_instancegroup
|
||||
* cloudstack: cs_ip_address
|
||||
* cloudstack: cs_loadbalancer_rule
|
||||
* cloudstack: cs_loadbalancer_rule_member
|
||||
* cloudstack: cs_network
|
||||
* cloudstack: cs_portforward
|
||||
* cloudstack: cs_project
|
||||
* cloudstack: cs_sshkeypair
|
||||
* cloudstack: cs_securitygroup
|
||||
* cloudstack: cs_securitygroup_rule
|
||||
* cloudstack: cs_staticnat
|
||||
* cloudstack: cs_template
|
||||
* cloudstack: cs_user
|
||||
* cloudstack: cs_vmsnapshot
|
||||
* cronvar
|
||||
* datadog_monitor
|
||||
* deploy_helper
|
||||
* docker: docker_login
|
||||
* dpkg_selections
|
||||
* elasticsearch_plugin
|
||||
* expect
|
||||
* find
|
||||
* google: gce_tag
|
||||
* hall
|
||||
* ipify_facts
|
||||
* iptables
|
||||
* libvirt: virt_net
|
||||
* libvirt: virt_pool
|
||||
* maven_artifact
|
||||
* openstack: os_auth
|
||||
* openstack: os_client_config
|
||||
* openstack: os_image
|
||||
* openstack: os_image_facts
|
||||
* openstack: os_floating_ip
|
||||
* openstack: os_ironic
|
||||
* openstack: os_ironic_node
|
||||
* openstack: os_keypair
|
||||
* openstack: os_network
|
||||
* openstack: os_network_facts
|
||||
* openstack: os_nova_flavor
|
||||
* openstack: os_object
|
||||
* openstack: os_port
|
||||
* openstack: os_project
|
||||
* openstack: os_router
|
||||
* openstack: os_security_group
|
||||
* openstack: os_security_group_rule
|
||||
* openstack: os_server
|
||||
* openstack: os_server_actions
|
||||
* openstack: os_server_facts
|
||||
* openstack: os_server_volume
|
||||
* openstack: os_subnet
|
||||
* openstack: os_subnet_facts
|
||||
* openstack: os_user
|
||||
* openstack: os_user_group
|
||||
* openstack: os_volume
|
||||
* openvswitch_db.
|
||||
* osx_defaults
|
||||
* pagerduty_alert
|
||||
* pam_limits
|
||||
* pear
|
||||
* profitbricks: profitbricks
|
||||
* profitbricks: profitbricks_datacenter
|
||||
* profitbricks: profitbricks_nic
|
||||
* profitbricks: profitbricks_volume
|
||||
* profitbricks: profitbricks_volume_attachments
|
||||
* profitbricks: profitbricks_snapshot
|
||||
* proxmox: proxmox
|
||||
* proxmox: proxmox_template
|
||||
* puppet
|
||||
* pushover
|
||||
* pushbullet
|
||||
* rax: rax_clb_ssl
|
||||
* rax: rax_mon_alarm
|
||||
* rax: rax_mon_check
|
||||
* rax: rax_mon_entity
|
||||
* rax: rax_mon_notification
|
||||
* rax: rax_mon_notification_plan
|
||||
* rabbitmq_binding
|
||||
* rabbitmq_exchange
|
||||
* rabbitmq_queue
|
||||
* selinux_permissive
|
||||
* sendgrid
|
||||
* sensu_check
|
||||
* sensu_subscription
|
||||
* seport
|
||||
* slackpkg
|
||||
* solaris_zone
|
||||
* taiga_issue
|
||||
* vertica_configuration
|
||||
* vertica_facts
|
||||
* vertica_role
|
||||
* vertica_schema
|
||||
* vertica_user
|
||||
* vmware: vca_fw
|
||||
* vmware: vca_nat
|
||||
* vmware: vmware_cluster
|
||||
* vmware: vmware_datacenter
|
||||
* vmware: vmware_dns_config
|
||||
* vmware: vmware_dvs_host
|
||||
* vmware: vmware_dvs_portgroup
|
||||
* vmware: vmware_dvswitch
|
||||
* vmware: vmware_host
|
||||
* vmware: vmware_migrate_vmk
|
||||
* vmware: vmware_portgroup
|
||||
* vmware: vmware_target_canonical_facts
|
||||
* vmware: vmware_vm_facts
|
||||
* vmware: vmware_vm_vss_dvs_migrate
|
||||
* vmware: vmware_vmkernel
|
||||
* vmware: vmware_vmkernel_ip_config
|
||||
* vmware: vmware_vsan_cluster
|
||||
* vmware: vmware_vswitch
|
||||
* vmware: vsphere_copy
|
||||
* webfaction_app
|
||||
* webfaction_db
|
||||
* webfaction_domain
|
||||
* webfaction_mailbox
|
||||
* webfaction_site
|
||||
* win_acl
|
||||
* win_dotnet_ngen
|
||||
* win_environment
|
||||
* win_firewall_rule
|
||||
* win_iis_virtualdirectory
|
||||
* win_iis_webapplication
|
||||
* win_iis_webapppool
|
||||
* win_iis_webbinding
|
||||
* win_iis_website
|
||||
* win_lineinfile
|
||||
* win_nssm
|
||||
* win_package
|
||||
* win_regedit
|
||||
* win_scheduled_task
|
||||
* win_unzip
|
||||
* win_updates
|
||||
* win_webpicmd
|
||||
* xenserver_facts
|
||||
* zabbix_host
|
||||
* zabbix_hostmacro
|
||||
* zabbix_screen
|
||||
* znode
|
||||
|
||||
####New Inventory scripts:
|
||||
|
||||
* cloudstack
|
||||
* fleetctl
|
||||
* openvz
|
||||
* nagios_ndo
|
||||
* nsot
|
||||
* proxmox
|
||||
* rudder
|
||||
* serf
|
||||
|
||||
####New Lookups:
|
||||
|
||||
* credstash
|
||||
* hashi_vault
|
||||
* ini
|
||||
* shelvefile
|
||||
|
||||
####New Filters:
|
||||
|
||||
* combine
|
||||
|
||||
####New Connection:
|
||||
|
||||
* docker: for talking to docker containers on the ansible controller machine without using ssh.
|
||||
|
||||
####New Callbacks:
|
||||
|
||||
* logentries: plugin to send play data to logentries service
|
||||
* skippy: same as default but does not display skip messages
|
||||
|
||||
###Minor changes:
|
||||
|
||||
* Many more tests. The new API makes things more testable and we took advantage of it.
|
||||
* big_ip modules now support turning off ssl certificate validation (use only for self-signed certificates).
|
||||
* Consolidated code from modules using urllib2 to normalize features, TLS and SNI support.
|
||||
* synchronize module's dest_port parameter now takes precedence over the ansible_ssh_port inventory setting.
|
||||
* Play output is now dynamically sized to terminal with a minimum of 80 coluumns (old default).
|
||||
* vars_prompt and pause are now skipped with a warning if the play is called non interactively (i.e. pull from cron).
|
||||
* Support for OpenBSD's 'doas' privilege escalation method.
|
||||
* Most vault operations can now be done over multilple files.
|
||||
* ansible-vault encrypt/decrypt read from stdin if no other input file is given, and can write to a given ``--output file`` (including stdout, '-').
|
||||
This lets you avoid ever writing sensitive plaintext to disk.
|
||||
* ansible-vault rekey accepts the --new-vault-password-file option.
|
||||
* ansible-vault now preserves file permissions on edit and rekey and defaults to restrictive permissions for other options.
|
||||
* Configuration items defined as paths (local only) now all support shell style interpolations.
|
||||
* Many fixes and new options added to modules, too many to list here.
|
||||
* Now you can see task file and line number when using verbosity of 3 or above.
|
||||
* The ``[x-y]`` host range syntax is no longer supported. Note that ``[0:1]`` matches two hosts, i.e. the range is inclusive of its endpoints.
|
||||
* We now recommend the Use `pattern1,pattern2` to combine host matching patterns.
|
||||
* The use of ':' as a separator conflicts with IPv6 addresses and host ranges. It will be deprecated in the future.
|
||||
* The undocumented use of ';' as a separator is now deprecated.
|
||||
* modules and callbacks have been extended to support no_log to avoid data disclosure.
|
||||
* new managed_syslog option has been added to control output to syslog on managed machines, no_log supercsedes this settings.
|
||||
* Lookup, vars and action plugin pathing has been normalized, all now follow the same sequence to find relative files.
|
||||
* We do not ignore the explicitly set login user for ssh when it matches the 'current user' anymore, this allows overriding .ssh/config when it is set
|
||||
explicitly. Leaving it unset will still use the same user and respect .ssh/config. This also means ansible_ssh_user can now return a None value.
|
||||
* environment variables passed to remote shells now default to 'controller' settings, with fallback to en_us.UTF8 which was the previous default.
|
||||
* add_hosts is much stricter about host name and will prevent invalid names from being added.
|
||||
* ansible-pull now defaults to doing shallow checkouts with git, use `--full` to return to previous behaviour.
|
||||
* random cows are more random
|
||||
* when: now gets the registered var after the first iteration, making it possible to break out of item loops
|
||||
* Handling of undefined variables has changed. In most places they will now raise an error instead of silently injecting an empty string. Use the default filter if you want to approximate the old behaviour:
|
||||
|
||||
```
|
||||
- debug: msg="The error message was: {{error_code |default('') }}"
|
||||
```
|
||||
|
||||
* The yum module's detection of installed packages has been made more robust by
|
||||
using /usr/bin/rpm in cases where it woud have used repoquery before.
|
||||
* The pip module now properly reports changes when packages are coming from a VCS.
|
||||
* Fixes for retrieving files over https when a CONNECT-only proxy is in the middle.
|
||||
|
||||
## 1.9.4 "Dancing In the Street" - Oct 9, 2015
|
||||
|
||||
* Fixes a bug where yum state=latest would error if there were no updates to install.
|
||||
* Fixes a bug where yum state=latest did not work with wildcard package names.
|
||||
* Fixes a bug in lineinfile relating to escape sequences.
|
||||
* Fixes a bug where vars_prompt was not keeping passwords private by default.
|
||||
* Fix ansible-galaxy and the hipchat callback plugin to check that the host it
|
||||
is contacting matches its TLS Certificate.
|
||||
|
||||
## 1.9.3 "Dancing In the Street" - Sep 3, 2015
|
||||
|
||||
* Fixes a bug related to keyczar messing up encodings internally, resulting in decrypted
|
||||
messages coming out as empty strings.
|
||||
* AES Keys generated for use in accelerated mode are now 256-bit by default instead of 128.
|
||||
* Fix url fetching for SNI with python-2.7.9 or greater. SNI does not work
|
||||
with python < 2.7.9. The best workaround is probably to use the command
|
||||
module with curl or wget.
|
||||
* Fix url fetching to allow tls-1.1 and tls-1.2 if the system's openssl library
|
||||
supports those protocols
|
||||
* Fix ec2_ami_search module to check TLS Certificates
|
||||
* Fix the following extras modules to check TLS Certificates:
|
||||
* campfire
|
||||
* layman
|
||||
* librarto_annotate
|
||||
* twilio
|
||||
* typetalk
|
||||
* Fix docker module's parsing of docker-py version for dev checkouts
|
||||
* Fix docker module to work with docker server api 1.19
|
||||
* Change yum module's state=latest feature to update all packages specified in
|
||||
a single transaction. This is the same type of fix as was made for yum's
|
||||
state=installed in 1.9.2 and both solves the same problems and with the same caveats.
|
||||
* Fixed a bug where stdout from a module might be blank when there were were non-printable
|
||||
ASCII characters contained within it
|
||||
|
||||
## 1.9.2 "Dancing In the Street" - Jun 26, 2015
|
||||
|
||||
* Security fixes to check that hostnames match certificates with https urls (CVE-2015-3908)
|
||||
- get_url and uri modules
|
||||
- url and etcd lookup plugins
|
||||
* Security fixes to the zone (Solaris containers), jail (bsd containers),
|
||||
and chroot connection plugins. These plugins can be used to connect to
|
||||
their respective container types in leiu of the standard ssh connection.
|
||||
Prior to this fix being applied these connection plugins didn't properly
|
||||
handle symlinks within the containers which could lead to files intended to
|
||||
be written to or read from the container being written to or read from the
|
||||
host system instead. (CVE pending)
|
||||
* Fixed a bug in the service module where init scripts were being incorrectly used instead of upstart/systemd.
|
||||
* Fixed a bug where sudo/su settings were not inherited from ansible.cfg correctly.
|
||||
* Fixed a bug in the rds module where a traceback may occur due to an unbound variable.
|
||||
* Fixed a bug where certain remote file systems where the SELinux context was not being properly set.
|
||||
* Re-enabled several windows modules which had been partially merged (via action plugins):
|
||||
- win_copy.ps1
|
||||
- win_copy.py
|
||||
- win_file.ps1
|
||||
- win_file.py
|
||||
- win_template.py
|
||||
* Fix bug using with_sequence and a count that is zero. Also allows counting backwards isntead of forwards
|
||||
* Fix get_url module bug preventing use of custom ports with https urls
|
||||
* Fix bug disabling repositories in the yum module.
|
||||
* Fix giving yum module a url to install a package from on RHEL/CENTOS5
|
||||
* Fix bug in dnf module preventing it from working when yum-utils was not already installed
|
||||
|
||||
## 1.9.1 "Dancing In the Street" - Apr 27, 2015
|
||||
|
||||
* Fixed a bug related to Kerberos auth when using winrm with a domain account.
|
||||
* Fixing several bugs in the s3 module.
|
||||
* Fixed a bug with upstart service detection in the service module.
|
||||
* Fixed several bugs with the user module when used on OSX.
|
||||
* Fixed unicode handling in some module situations (assert and shell/command execution).
|
||||
* Fixed a bug in redhat_subscription when using the activationkey parameter.
|
||||
* Fixed a traceback in the gce module on EL6 distros when multiple pycrypto installations are available.
|
||||
* Added support for PostgreSQL 9.4 in rds_param_group
|
||||
* Several other minor fixes.
|
||||
|
||||
## 1.9 "Dancing In the Street" - Mar 25, 2015
|
||||
|
||||
Major changes:
|
||||
|
||||
* Added kerberos support to winrm connection plugin.
|
||||
* Tags rehaul: added 'all', 'always', 'untagged' and 'tagged' special tags and normalized
|
||||
tag resolution. Added tag information to --list-tasks and new --list-tags option.
|
||||
* Privilege Escalation generalization, new 'Become' system and variables now will
|
||||
handle existing and new methods. Sudo and su have been kept for backwards compatibility.
|
||||
New methods pbrun and pfexec in 'alpha' state, planned adding 'runas' for winrm connection plugin.
|
||||
* Improved ssh connection error reporting, now you get back the specific message from ssh.
|
||||
* Added facility to document task module return values for registered vars, both for
|
||||
ansible-doc and the docsite. Documented copy, stats and acl modules, the rest must be
|
||||
updated individually (we will start doing so incrementally).
|
||||
* Optimize the plugin loader to cache available plugins much more efficiently.
|
||||
For some use cases this can lead to dramatic improvements in startup time.
|
||||
* Overhaul of the checksum system, now supports more systems and more cases more reliably and uniformly.
|
||||
* Fix skipped tasks to not display their parameters if no_log is specified.
|
||||
* Many fixes to unicode support, standarized functions to make it easier to add to input/output boundaries.
|
||||
* Added travis integration to github for basic tests, this should speed up ticket triage and merging.
|
||||
* environment: directive now can also be applied to play and is inhertited by tasks, which can still override it.
|
||||
* expanded facts and OS/distribution support for existing facts and improved performance with pypy.
|
||||
* new 'wantlist' option to lookups allows for selecting a list typed variable vs a comma delimited string as the return.
|
||||
* the shared module code for file backups now uses a timestamp resolution of seconds (previouslly minutes).
|
||||
* allow for empty inventories, this is now a warning and not an error (for those using localhost and cloud modules).
|
||||
* sped up YAML parsing in ansible by up to 25% by switching to CParser loader.
|
||||
|
||||
New Modules:
|
||||
|
||||
* cryptab: manages linux encrypted block devices
|
||||
* gce_img: for utilizing GCE image resources
|
||||
* gluster_volume: manage glusterfs volumes
|
||||
* haproxy: for the load balancer of same name
|
||||
* known_hosts: manages the ssh known_hosts file
|
||||
* lxc_container: manage lxc containers
|
||||
* patch: allows for patching files on target systems
|
||||
* pkg5: installing and uninstalling packages on Solaris
|
||||
* pkg5_publisher: manages Solaris pkg5 repository configuration
|
||||
* postgresql_ext: manage postgresql extensions
|
||||
* snmp_facts: gather facts via snmp
|
||||
* svc: manages daemontools based services
|
||||
* uptimerobot: manage monitoring with this service
|
||||
|
||||
New Filters:
|
||||
|
||||
* ternary: allows for trueval/falseval assignment dependent on conditional
|
||||
* cartesian: returns the Cartesian product of 2 lists
|
||||
* to_uuid: given a string it will return an ansible domain specific UUID
|
||||
* checksum: uses the ansible internal checksum to return a hash from a string
|
||||
* hash: get a hash from a string (md5, sha1, etc)
|
||||
* password_hash: get a hash form as string that can be used as a password in the user module (and others)
|
||||
* A whole set of ip/network manipulation filters: ipaddr,ipwrap,ipv4,ipv6ipsubnet,nthhost,hwaddr,macaddr
|
||||
|
||||
Other Notable Changes:
|
||||
|
||||
* New lookup plugins:
|
||||
* dig: does dns resolution and returns IPs.
|
||||
* url: allows pulling data from a url.
|
||||
|
||||
* New callback plugins:
|
||||
* syslog_json: allows logging play output to a syslog network server using json format
|
||||
|
||||
* Many new enhancements to the amazon web service modules:
|
||||
* ec2 now applies all specified security groups when creating a new instance. Previously it was only applying one
|
||||
* ec2_vol gained the ability to specify the EBS volume type
|
||||
* ec2_vol can now detach volumes by specifying instance=None
|
||||
* Fix ec2_group to purge specific grants rather than whole rules
|
||||
* Added tenancy support for the ec2 module
|
||||
* rds module has gained the ability to manage tags and set charset and public accessibility
|
||||
* ec2_snapshot module gained the capability to remove snapshots
|
||||
* Add alias support for route53
|
||||
* Add private_zones support to route53
|
||||
* ec2_asg: Add wait_for_instances parameter that waits until an instance is ready before ending the ansible task
|
||||
* Many new docker improvements:
|
||||
* restart_policy parameters to configure when the container automatically restarts
|
||||
* If the docker client or server doesn't support an option, the task will now fail instead of silently ignoring the option
|
||||
* Add insecure_registry parameter for connecting to registries via http
|
||||
* New parameter to set a container's domain name
|
||||
* Undeprecated docker_image module until there's replacement functionality
|
||||
* Allow setting the container's pid namespace
|
||||
* Add a pull parameter that chooses when ansible will look for more recent images in the registry
|
||||
* docker module states have been greatly enhanced. The reworked and new states are:
|
||||
* present now creates but does not start containers
|
||||
* restarted always restarts a container
|
||||
* reloaded restarts a container if ansible detects that the configuration is different than what is specified
|
||||
* reloaded accounts for exposed ports, env vars, and volumes
|
||||
* Can now connect to the docker server using TLS
|
||||
* Several source control modules had force parameters that defaulted to true.
|
||||
These have been changed to default to false so as not to accidentally lose
|
||||
work. Playbooks that depended on the former behaviour simply need to add
|
||||
force=True to the task that needs it. Affected modules:
|
||||
* bzr: When local modifications exist in a checkout, the bzr module used to
|
||||
default to removing the modifications on any operation. Now the module
|
||||
will not remove the modifications unless force=yes is specified.
|
||||
Operations that depend on a clean working tree may fail unless force=yes is
|
||||
added.
|
||||
* git: When local modifications exist in a checkout, the git module will now
|
||||
fail unless force is explicitly specified. Specifying force=yes will allow
|
||||
the module to revert and overwrite local modifications to make git actions
|
||||
succeed.
|
||||
* hg: When local modifications exist in a checkout, the hg module used to
|
||||
default to removing the modifications on any operation. Now the module
|
||||
will not remove the modifications unless force=yes is specified.
|
||||
* subversion: When updating a checkout with local modifications, you now need
|
||||
to add force=yes so the module will revert the modifications before updating.
|
||||
* New inventory scripts:
|
||||
* vbox: virtualbox
|
||||
* consul: use consul as an inventory source
|
||||
* gce gained the ip_forward parameter to forward ip packets
|
||||
* disk_auto_delete parameter to gce that will remove the boot disk after an instance is destroyed
|
||||
* gce can now spawn instances with no external ip
|
||||
* gce_pd gained the ability to choose a disk type
|
||||
* gce_net gained target_tags parameter for creating firewall rules
|
||||
* rax module has new parameters for making use of a boot volume
|
||||
* Add scheduler_hints to the nova_compute module for optional parameters
|
||||
* vsphere_guest now supports deploying guests from a template
|
||||
* Many fixes for hardlink and softlink handling in file-related modules
|
||||
* Implement user, group, mode, and selinux parameters for the unarchive module
|
||||
* authorized_keys can now use url as a key source
|
||||
* authorized_keys has a new exclusive parameter that determines if keys that weren't specified in the task
|
||||
* The selinux module now sets the current running state to permissive if state='disabled'
|
||||
* Can now set accounts to expire via the user module
|
||||
* Overhaul of the service module to make code simpler and behave better for systems running several popular init systems
|
||||
* yum module now has a parameter to refresh its cache of package metadata
|
||||
* apt module gained a build_dep parameter to install a package's build dependencies
|
||||
* Add parameters to the postgres modules to specify a unix socket to connect to the db
|
||||
* The mount module now supports bind mounts
|
||||
* Add a clone parameter to git module that allows you to get information about a remote repo even if it doesn't exist locally.
|
||||
* Add a refspec argument to the git module that allows pulling commits that aren't part of a branch
|
||||
* Many documentation additions and fixes.
|
||||
|
||||
## 1.8.4 "You Really Got Me" - Feb 19, 2015
|
||||
|
||||
* Fixed regressions in ec2 and mount modules, introduced in 1.8.3
|
||||
|
||||
## 1.8.3 "You Really Got Me" - Feb 17, 2015
|
||||
|
||||
* Fixing a security bug related to the default permissions set on a temporary file created when using "ansible-vault view <filename>".
|
||||
* Many bug fixes, for both core code and core modules.
|
||||
|
||||
## 1.8.2 "You Really Got Me" - Dec 04, 2014
|
||||
|
||||
* Various bug fixes for packaging issues related to modules.
|
||||
* Various bug fixes for lookup plugins.
|
||||
* Various bug fixes for some modules (continued cleanup of postgresql issues, etc.).
|
||||
|
||||
* Add a clone parameter to git module that allows you to get information about a remote repo even if it doesn't exist locally.
|
||||
|
||||
## 1.8.1 "You Really Got Me" - Nov 26, 2014
|
||||
|
||||
* Various bug fixes in postgresql and mysql modules.
|
||||
* Fixed a bug related to lookup plugins used within roles not finding files based on the relative paths to the roles files/ directory.
|
||||
* Fixed a bug related to vars specified in plays being templated too early, resulting in incorrect variable interpolation.
|
||||
* Fixed a bug related to git submodules in bare repos.
|
||||
|
||||
## 1.8 "You Really Got Me" - Nov 25, 2014
|
||||
|
||||
Major changes:
|
||||
|
||||
* fact caching support, pluggable, initially supports Redis (DOCS pending)
|
||||
* 'serial' size in a rolling update can be specified as a percentage
|
||||
* added new Jinja2 filters, 'min' and 'max' that take lists
|
||||
* new 'ansible_version' variable available contains a dictionary of version info
|
||||
* For ec2 dynamic inventory, ec2.ini can has various new configuration options
|
||||
* 'ansible vault view filename.yml' opens filename.yml decrypted in a pager.
|
||||
* no_log parameter now surpressess data from callbacks/output as well as syslog
|
||||
* ansible-galaxy install -f requirements.yml allows advanced options and installs from non-galaxy SCM sources and tarballs.
|
||||
* command_warnings feature will warn about when usage of the shell/command module can be simplified to use core modules - this can be enabled in ansible.cfg
|
||||
* new omit value can be used to leave off a parameter when not set, like so module_name: a=1 b={{ c | default(omit) }}, would not pass value for b (not even an empty value) if c was not set.
|
||||
* developers: 'baby JSON' in module responses, originally intended for writing modules in bash, is removed as a feature to simplify logic, script module remains available for running bash scripts.
|
||||
* async jobs started in "fire & forget" mode can now be checked on at a later time.
|
||||
* added ability to subcategorize modules for docs.ansible.com
|
||||
* added ability for shipped modules to have aliases with symlinks
|
||||
* added ability to deprecate older modules by starting with "_" and including "deprecated: message why" in module docs
|
||||
|
||||
New Modules:
|
||||
|
||||
* cloud: rax_cdb - manages Rackspace Cloud Database instances
|
||||
* cloud: rax_cdb_database - manages Rackspace Cloud Databases
|
||||
* cloud: rax_cdb_user - manages Rackspace Cloud Database users
|
||||
* monitoring: zabbix_maintaince - handles outage windows with Zabbix
|
||||
* monitoring: bigpanda - support for bigpanda
|
||||
* net_infrastructure: a10_server - manages server objects on A10 devices
|
||||
* net_infrastructure: a10_service_group - manages service group objects on A10 devices
|
||||
* net_infrastructure: a10_virtual_server - manages virtual server objects on A10 devices
|
||||
* system: getent - read getent databases
|
||||
|
||||
Some other notable changes:
|
||||
|
||||
* added the ability to set "instance filters" in the ec2.ini to limit results from the inventory plugin.
|
||||
* upgrades for various variable precedence items and parsing related items
|
||||
* added a new "follow" parameter to the file and copy modules, which allows actions to be taken on the target of a symlink rather than the symlink itself.
|
||||
* if a module should ever traceback, it will return a standard error, catchable by ignore_errors, versus an 'unreachable'
|
||||
* ec2_lc: added support for multiple new parameters like kernel_id, ramdisk_id and ebs_optimized.
|
||||
* ec2_elb_lb: added support for the connection_draining_timeout and cross_az_load_balancing options.
|
||||
* support for symbolic representations (ie. u+rw) for file permission modes (file/copy/template modules etc.).
|
||||
* docker: Added support for specifying the net type of the container.
|
||||
* docker: support for specifying read-only volumes.
|
||||
* docker: support for specifying the API version to use for the remote connection.
|
||||
* openstack modules: various improvements
|
||||
* irc: ssl support for the notification module
|
||||
* npm: fix flags passed to package installation
|
||||
* windows: improved error handling
|
||||
* setup: additional facts on System Z
|
||||
* apt_repository: certificate validation can be disabled if requested
|
||||
* pagerduty module: misc improvements
|
||||
* ec2_lc: public_ip boolean configurable in launch configurations
|
||||
* ec2_asg: fixes related to proper termination of an autoscaling group
|
||||
* win_setup: total memory fact correction
|
||||
* ec2_vol: ability to list existing volumes
|
||||
* ec2: can set optimized flag
|
||||
* various parser improvements
|
||||
* produce a friendly error message if the SSH key is too permissive
|
||||
* ec2_ami_search: support for SSD and IOPS provisioned EBS images
|
||||
* can set ansible_sudo_exe as an inventory variable which allows specifying
|
||||
a different sudo (or equivalent) command
|
||||
* git module: Submodule handling has changed. Previously if you used the
|
||||
``recursive`` parameter to handle submodules, ansible would track the
|
||||
submodule upstream's head revision. This has been changed to checkout the
|
||||
version of the submodule specified in the superproject's git repository.
|
||||
This is inline with what git submodule update does. If you want the old
|
||||
behaviour use the new module parameter track_submodules=yes
|
||||
* Checksumming of transferred files has been made more portable and now uses
|
||||
the sha1 algorithm instead of md5 to be compatible with FIPS-140.
|
||||
- As a small side effect, the fetch module no longer returns a useful value
|
||||
in remote_md5. If you need a replacement, switch to using remote_checksum
|
||||
which returns the sha1sum of the remote file.
|
||||
* ansible-doc CLI tool contains various improvements for working with different terminals
|
||||
|
||||
And various other bug fixes and improvements ...
|
||||
|
||||
|
||||
## 1.7.2 "Summer Nights" - Sep 24, 2014
|
||||
|
||||
- Fixes a bug in accelerate mode which caused a traceback when trying to use that connection method.
|
||||
- Fixes a bug in vault where the password file option was not being used correctly internally.
|
||||
- Improved multi-line parsing when using YAML literal blocks (using > or |).
|
||||
- Fixed a bug with the file module and the creation of relative symlinks.
|
||||
- Fixed a bug where checkmode was not being honoured during the templating of files.
|
||||
- Other various bug fixes.
|
||||
|
||||
## 1.7.1 "Summer Nights" - Aug 14, 2014
|
||||
|
||||
- Security fix to disallow specifying 'args:' as a string, which could allow the insertion of extra module parameters through variables.
|
||||
- Performance enhancements related to previous security fixes, which could cause slowness when modules returned very large JSON results. This specifically impacted the unarchive module frequently, which returns the details of all unarchived files in the result.
|
||||
- Docker module bug fixes:
|
||||
* Fixed support for specifying rw/ro bind modes for volumes
|
||||
* Fixed support for allowing the tag in the image parameter
|
||||
- Various other bug fixes
|
||||
|
||||
## 1.7 "Summer Nights" - Aug 06, 2014
|
||||
|
||||
Major new features:
|
||||
|
||||
* Windows support (alpha) using native PowerShell remoting
|
||||
* Tasks can now specify `run_once: true`, meaning they will be executed exactly once. This can be combined with delegate_to to trigger actions you want done just the one time versus for every host in inventory.
|
||||
|
||||
New inventory scripts:
|
||||
|
||||
* SoftLayer
|
||||
* Windows Azure
|
||||
|
||||
New Modules:
|
||||
|
||||
* cloud: azure
|
||||
* cloud: rax_meta
|
||||
* cloud: rax_scaling_group
|
||||
* cloud: rax_scaling_policy
|
||||
* windows: version of setup module
|
||||
* windows: version of slurp module
|
||||
* windows: win_feature
|
||||
* windows: win_get_url
|
||||
* windows: win_msi
|
||||
* windows: win_ping
|
||||
* windows: win_user
|
||||
* windows: win_service
|
||||
* windows: win_group
|
||||
|
||||
Other notable changes:
|
||||
|
||||
* Security fixes
|
||||
- Prevent the use of lookups when using legacy "{{ }}" syntax around variables and with_* loops.
|
||||
- Remove relative paths in TAR-archived file names used by ansible-galaxy.
|
||||
* Inventory speed improvements for very large inventories.
|
||||
* Vault password files can now be executable, to support scripts that fetch the vault password.
|
||||
|
||||
|
||||
## 1.6.10 "And the Cradle Will Rock" - Jul 25, 2014
|
||||
|
||||
- Fixes an issue with the copy module when copying a directory that fails when changing file attributes and the target file already exists
|
||||
- Improved unicode handling when splitting args
|
||||
|
||||
## 1.6.9 "And the Cradle Will Rock" - Jul 24, 2014
|
||||
|
||||
- Further improvements to module parameter parsing to address additional regressions caused by security fixes
|
||||
|
||||
## 1.6.8 "And the Cradle Will Rock" - Jul 22, 2014
|
||||
|
||||
- Corrects a regression in the way shell and command parameters were being parsed
|
||||
|
||||
## 1.6.7 "And the Cradle Will Rock" - Jul 21, 2014
|
||||
|
||||
- Security fixes:
|
||||
* Strip lookup calls out of inventory variables and clean unsafe data
|
||||
returned from lookup plugins (CVE-2014-4966)
|
||||
* Make sure vars don't insert extra parameters into module args and prevent
|
||||
duplicate params from superseding previous params (CVE-2014-4967)
|
||||
|
||||
## 1.6.6 "And the Cradle Will Rock" - Jul 01, 2014
|
||||
|
||||
- Security updates to further protect against the incorrect execution of untrusted data
|
||||
|
||||
## 1.6.4, 1.6.5 "And the Cradle Will Rock" - Jun 25, 2014
|
||||
|
||||
- Security updates related to evaluation of untrusted remote inputs
|
||||
- Security update to prevent local operations from executing as the result of specifically crafted untrusted data
|
||||
|
||||
## 1.6.3 "And the Cradle Will Rock" - Jun 09, 2014
|
||||
|
||||
@ -895,7 +19,7 @@ Other notable changes:
|
||||
- Various other minor bug fixes.
|
||||
|
||||
## 1.6.2 "And the Cradle Will Rock" - May 23, 2014
|
||||
|
||||
|
||||
- If an improper locale is specified, core modules will now automatically revert to using the 'C' locale.
|
||||
- Modules using the fetch_url utility will now obey proxy environment variables.
|
||||
- The SSL validation step in fetch_url will likewise obey proxy settings, however only proxies using the http protocol are supported.
|
||||
@ -994,7 +118,7 @@ Other notable changes:
|
||||
## 1.5.4 "Love Walks In" - April 1, 2014
|
||||
|
||||
- Security fix for safe_eval, which further hardens the checking of the evaluation function.
|
||||
- Changing order of variable precedence for system facts, to ensure that inventory variables take precedence over any facts that may be set on a host.
|
||||
- Changing order of variable precendence for system facts, to ensure that inventory variables take precedence over any facts that may be set on a host.
|
||||
|
||||
## 1.5.3 "Love Walks In" - March 13, 2014
|
||||
|
||||
@ -1029,7 +153,7 @@ Major features/changes:
|
||||
* ec2 module now accepts 'exact_count' and 'count_tag' as a way to enforce a running number of nodes by tags.
|
||||
* all ec2 modules that work with Eucalyptus also now support a 'validate_certs' option, which can be set to 'off' for installations using self-signed certs.
|
||||
* Start of new integration test infrastructure (WIP, more details TBD)
|
||||
* if repoquery is unavailable, the yum module will automatically attempt to install yum-utils
|
||||
* if repoquery is unavailble, the yum module will automatically attempt to install yum-utils
|
||||
* ansible-vault: a framework for encrypting your playbooks and variable files
|
||||
* added support for privilege escalation via 'su' into bin/ansible and bin/ansible-playbook and associated keywords 'su', 'su_user', 'su_pass' for tasks/plays
|
||||
|
||||
@ -1049,7 +173,7 @@ New modules:
|
||||
* system: at
|
||||
* utilities: assert
|
||||
|
||||
Other notable changes (many new module params & bugfixes may not be listed):
|
||||
Other notable changes (many new module params & bugfixes may not not listed):
|
||||
|
||||
* no_reboot is now defaulted to "no" in the ec2_ami module to ensure filesystem consistency in the resulting AMI.
|
||||
* sysctl module overhauled
|
||||
@ -1109,7 +233,7 @@ Highlighted new features:
|
||||
* Added do-until feature, which can be used to retry a failed task a specified number of times with a delay in-between the retries.
|
||||
* Added failed_when option for tasks, which can be used to specify logical statements that make it easier to determine when a task has failed, or to make it easier to ignore certain non-zero return codes for some commands.
|
||||
* Added the "subelement" lookup plugin, which allows iteration of the keys of a dictionary or items in a list.
|
||||
* Added the capability to use either paramiko or ssh for the initial setup connection of an accelerated playbook.
|
||||
* Added the capability to use either paramiko or ssh for the inital setup connection of an accelerated playbook.
|
||||
* Automatically provide advice on common parser errors users encounter.
|
||||
* Deprecation warnings are now shown for legacy features: when_integer/etc, only_if, include+with_items, etc. Can be disabled in ansible.cfg
|
||||
* The system will now provide helpful tips around possible YAML syntax errors increasing ease of use for new users.
|
||||
@ -1179,7 +303,7 @@ Misc changes (all module additions/fixes may not listed):
|
||||
* Added a -vvvv level, which will show SSH client debugging information in the event of a failure.
|
||||
* Includes now support the more standard syntax, similar to that of role includes and dependencies.
|
||||
* Changed the `user:` parameter on plays to `remote_user:` to prevent confusion with the module of the same name. Still backwards compatible on play parameters.
|
||||
* Added parameter to allow the fetch module to skip the md5 validation step ('validate_md5=false'). This is useful when fetching files that are actively being written to, such as live log files.
|
||||
* Added parameter to allow the fetch module to skip the md5 validation step ('validate_md5=false'). This is usefull when fetching files that are actively being written to, such as live log files.
|
||||
* Inventory hosts are used in the order they appear in the inventory.
|
||||
* in hosts: foo[2-5] type syntax, the iterators now are zero indexed and the last index is non-inclusive, to match Python standards.
|
||||
* There is now a way for a callback plugin to disable itself. See osx_say example code for an example.
|
||||
@ -1438,7 +562,7 @@ Modules added:
|
||||
* packages: redhat_subscription: manage Red Hat subscription usage
|
||||
* packages: rhn_register: basic RHN registration
|
||||
* packages: zypper (SuSE)
|
||||
* database: postgresql_priv: manages postgresql privileges
|
||||
* database: postgresql_priv: manages postgresql priveledges
|
||||
* networking: bigip_pool: load balancing with F5s
|
||||
* networking: ec2_elb: add and remove machines from ec2 elastic load balancers
|
||||
* notification: hipchat: send notification events to hipchat
|
||||
@ -1480,7 +604,7 @@ Bugfixes and Misc Changes:
|
||||
* private_ip parameter added to the ec2 module
|
||||
* $FILE and $PIPE now tolerate unicode
|
||||
* various plugin loading operations have been made more efficient
|
||||
* hostname now uses platform.node versus socket.gethostname to be more consistent with Unix 'hostname'
|
||||
* hostname now uses platform.node versus socket.gethostname to be more consistant with Unix 'hostname'
|
||||
* fix for SELinux operations on Unicode path names
|
||||
* inventory directory locations now ignore files with .ini extensions, making hybrid inventory easier
|
||||
* copy module in check-mode now reports back correct changed status when used with force=no
|
||||
@ -1492,7 +616,7 @@ Bugfixes and Misc Changes:
|
||||
* misc fixes to the Riak module
|
||||
* make template module slightly more efficient
|
||||
* base64encode / decode filters are now available to templates
|
||||
* libvirt module can now work with multiple different libvirt connection URIs
|
||||
* libvirt module can now work with multiple different libvirt connecton URIs
|
||||
* fix for postgresql password escaping
|
||||
* unicode fix for shlex.split in some cases
|
||||
* apt module upgrade logic improved
|
||||
@ -1520,14 +644,14 @@ the variable is still registered for the host, with the attribute skipped: True.
|
||||
* localhost and 127.0.0.1 are now fuzzy matched in inventory (are now more or less interchangeable)
|
||||
* AIX improvements/fixes for users, groups, facts
|
||||
* lineinfile now does atomic file replacements
|
||||
* fix to not pass PasswordAuthentication=no in the config file unnecessarily for SSH connection type
|
||||
* for authorized_key on Debian Squeeze
|
||||
* fix to not pass PasswordAuthentication=no in the config file unneccessarily for SSH connection type
|
||||
* for for authorized_key on Debian Squeeze
|
||||
* fixes for apt_repository module reporting changed incorrectly on certain repository types
|
||||
* allow the virtualenv argument to the pip module to be a pathname
|
||||
* service pattern argument now correctly read for BSD services
|
||||
* fetch location can now be controlled more directly via the 'flat' parameter.
|
||||
* added basename and dirname as Jinja2 filters available to all templates
|
||||
* pip works better when sudoing from unprivileged users
|
||||
* pip works better when sudoing from unpriveledged users
|
||||
* fix for user creation with groups specification reporting 'changed' incorrectly in some cases
|
||||
* fix for some unicode encoding errors in outputing some data in verbose mode
|
||||
* improved FreeBSD, NetBSD and Solaris facts
|
||||
@ -1697,7 +821,7 @@ New playbook/language features:
|
||||
* task includes can now be of infinite depth
|
||||
* when_set and when_unset can take more than one var (when_set: $a and $b and $c)
|
||||
* added the with_sequence lookup plugin
|
||||
* can override "connection:" on an individual task
|
||||
* can override "connection:" on an indvidual task
|
||||
* parameterized playbook includes can now define complex variables (not just all on one line)
|
||||
* making inventory variables available for use in vars_files paths
|
||||
* messages when skipping plays are now more clear
|
||||
@ -1947,7 +1071,7 @@ Module changes:
|
||||
* setup module now detects interfaces with aliases
|
||||
* better handling of VM guest type detection in setup module
|
||||
* new module boilerplate code to check for mutually required arguments, arguments required together, exclusive args
|
||||
* add pattern= as a parameter to the service module (for init scripts that don't do status, or do poor status)
|
||||
* add pattern= as a paramter to the service module (for init scripts that don't do status, or do poor status)
|
||||
* various fixes to mysql & postresql modules
|
||||
* added a thirsty= option (boolean, default no) to the get_url module to decide to download the file every time or not
|
||||
* added a wait_for module to poll for ports being open
|
||||
|
||||
@ -120,7 +120,7 @@ Line up variables
|
||||
Don't use line continuations:
|
||||
|
||||
# no
|
||||
if (this_is_a_very_long_line and foo and \
|
||||
if (this_is_a_very_long_line and foo and /
|
||||
i_am_going_to_continue_it):
|
||||
bar()
|
||||
|
||||
|
||||
229
CONTRIBUTING.md
229
CONTRIBUTING.md
@ -1,27 +1,224 @@
|
||||
Welcome To Ansible GitHub
|
||||
Ansible Community Information
|
||||
==============================
|
||||
|
||||
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).
|
||||
|
||||
|
||||
Contributing Code (Features or Bugfixes)
|
||||
----------------------------------------
|
||||
|
||||
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).
|
||||
|
||||
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!
|
||||
|
||||
|
||||
Reporting A Bug
|
||||
---------------
|
||||
|
||||
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 recieve 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
|
||||
=========================
|
||||
|
||||
Hi! Nice to see you here!
|
||||
Staff
|
||||
-----
|
||||
|
||||
If you'd like to ask a question
|
||||
===============================
|
||||
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
|
||||
|
||||
Please see [this web page ](http://docs.ansible.com/community.html) for community information, which includes pointers on how to ask questions on the [mailing lists](http://docs.ansible.com/community.html#mailing-list-information) and IRC.
|
||||
On IRC, you can find us as mdehaan, jimi_c, Tybstar, and others. On the mailing list,
|
||||
we post with an @ansible.com address.
|
||||
|
||||
The github issue tracker is not the best place for questions for various reasons, but both IRC and the mailing list are very helpful places for those things, and that page has the pointers to those.
|
||||
Community Code of Conduct
|
||||
-------------------------
|
||||
|
||||
If you'd like to contribute code
|
||||
================================
|
||||
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).
|
||||
|
||||
Please see [this web page](http://docs.ansible.com/community.html) for information about the contribution process. Important license agreement information is also included on that page.
|
||||
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.
|
||||
|
||||
If you'd like to file a bug
|
||||
===========================
|
||||
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.
|
||||
|
||||
I'd also read the community page above, but in particular, make sure you copy [this issue template](https://github.com/ansible/ansible/blob/devel/ISSUE_TEMPLATE.md) into your ticket description. We have a friendly neighborhood bot that will remind you if you forget :) This template helps us organize tickets faster and prevents asking some repeated questions, so it's very helpful to us and we appreciate your help with it.
|
||||
|
||||
Also please make sure you are testing on the latest released version of Ansible or the development branch.
|
||||
|
||||
Thanks!
|
||||
|
||||
|
||||
|
||||
@ -1,39 +0,0 @@
|
||||
##### Issue Type:
|
||||
|
||||
Can you help us out in labelling this by telling us what kind of ticket this this? You can say:
|
||||
- Bug Report
|
||||
- Feature Idea
|
||||
- Feature Pull Request
|
||||
- New Module Pull Request
|
||||
- Bugfix Pull Request
|
||||
- Documentation Report
|
||||
- Docs Pull Request
|
||||
|
||||
##### Ansible Version:
|
||||
|
||||
Let us know what version of Ansible you are using. Please supply the verbatim output from running “ansible --version”. If you're filing a ticket on a version of Ansible which is not the latest, we'd greatly appreciate it if you could retest on the latest version first. We don't expect you to test against the development branch most of the time, but we may ask for that if you have cycles. Thanks!
|
||||
|
||||
##### Ansible Configuration:
|
||||
|
||||
What have you changed about your Ansible installation? What configuration settings have you changed/added/removed? Compare your /etc/ansible/ansible.cfg against a clean version from Github and let us know what's different.
|
||||
|
||||
##### Environment:
|
||||
|
||||
What OS are you running Ansible from and what OS are you managing? Examples include RHEL 5/6, Centos 5/6, Ubuntu 12.04/13.10, *BSD, Solaris. If this is a generic feature request or it doesn’t apply, just say “N/A”. Not all tickets may be about operating system related things and we understand that.
|
||||
|
||||
##### Summary:
|
||||
|
||||
Please summarize your request in this space. You will earn bonus points for being succinct, but please add enough detail so we can understand the request. Thanks!
|
||||
|
||||
##### Steps To Reproduce:
|
||||
|
||||
If this is a bug ticket, please enter the steps you use to reproduce the problem in the space below. If this is a feature request, please enter the steps you would use to use the feature. If an example playbook is useful, please include a short reproducer inline, indented by four spaces. If a longer one is necessary, linking to one uploaded to gist.github.com would be great. Much appreciated!
|
||||
|
||||
##### Expected Results:
|
||||
|
||||
Please enter your expected results in this space. When running the steps supplied above in the previous section, what did you expect to happen? If showing example output, please indent your output by four spaces so it will render correctly in GitHub's viewer thingy.
|
||||
|
||||
##### Actual Results:
|
||||
|
||||
Please enter your actual results in this space. When running the steps supplied above, what actually happened? If you are showing example output, please indent your output by four spaces so it will render correctly in GitHub. Thanks again!
|
||||
|
||||
24
MANIFEST.in
24
MANIFEST.in
@ -1,24 +1,8 @@
|
||||
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
|
||||
include lib/ansible/module_utils/powershell.ps1
|
||||
recursive-include lib/ansible/modules *
|
||||
recursive-include lib/ansible/galaxy/data *
|
||||
graft examples/playbooks
|
||||
include packaging/distutils/setup.py
|
||||
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
|
||||
|
||||
39
Makefile
39
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
|
||||
|
||||
########################################################
|
||||
@ -34,17 +34,11 @@ 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'),1)
|
||||
@ -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 = 0.git$(DATE)$(GITINFO)
|
||||
DEB_RELEASE = 0.git$(DATE)
|
||||
# Do not sign unofficial builds
|
||||
DEBUILD_OPTS += -uc -us
|
||||
DPUT_OPTS += -u
|
||||
@ -81,9 +74,9 @@ DEB_DIST ?= unstable
|
||||
RPMSPECDIR= packaging/rpm
|
||||
RPMSPEC = $(RPMSPECDIR)/ansible.spec
|
||||
RPMDIST = $(shell rpm --eval '%{?dist}')
|
||||
RPMRELEASE = $(RELEASE)
|
||||
ifneq ($(OFFICIAL),yes)
|
||||
RPMRELEASE = 0.git$(DATE)$(GITINFO)
|
||||
RPMRELEASE = 1
|
||||
ifeq ($(OFFICIAL),)
|
||||
RPMRELEASE = 0.git$(DATE)
|
||||
endif
|
||||
RPMNVR = "$(NAME)-$(VERSION)-$(RPMRELEASE)$(RPMDIST)"
|
||||
|
||||
@ -93,17 +86,12 @@ MOCK_CFG ?=
|
||||
|
||||
NOSETESTS ?= nosetests
|
||||
|
||||
NOSETESTS3 ?= nosetests-3.4
|
||||
|
||||
########################################################
|
||||
|
||||
all: clean python
|
||||
|
||||
tests:
|
||||
PYTHONPATH=./lib $(NOSETESTS) -d -w test/units -v --with-coverage --cover-package=ansible --cover-branches
|
||||
|
||||
tests-py3:
|
||||
PYTHONPATH=./lib $(NOSETESTS3) -d -w test/units -v --with-coverage --cover-package=ansible --cover-branches
|
||||
PYTHONPATH=./lib ANSIBLE_LIBRARY=./library $(NOSETESTS) -d -w test/units -v
|
||||
|
||||
authors:
|
||||
sh hacking/authors.sh
|
||||
@ -126,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/*
|
||||
@ -135,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
|
||||
@ -156,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
|
||||
@ -167,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/
|
||||
@ -224,7 +207,7 @@ 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: debian
|
||||
|
||||
44
README.md
44
README.md
@ -1,30 +1,25 @@
|
||||
[](https://pypi.python.org/pypi/ansible)
|
||||
[](https://pypi.python.org/pypi/ansible)
|
||||
[](https://travis-ci.org/ansible/ansible)
|
||||
[](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 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](http://docs.ansible.com/intro_getting_started.html) for a variety of platforms. If you decide to go with the development branch, be sure to run `git submodule update --init --recursive` after doing a checkout.
|
||||
|
||||
If you want to download a tarball of a release, go to [releases.ansible.com](http://releases.ansible.com/ansible), though most users use `yum` (using the EPEL instructions linked above), `apt` (using the PPA instructions linked above), or `pip install ansible`.
|
||||
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.
|
||||
@ -32,27 +27,22 @@ Design Principles
|
||||
Get Involved
|
||||
============
|
||||
|
||||
* Read [Community Information](http://docs.ansible.com/community.html) for all kinds of ways to contribute to and interact with the project, including mailing list information and how to submit bug reports and code to Ansible.
|
||||
* All code submissions are done through pull requests. Take care to make sure no merge commits are in the submission, and use `git rebase` vs `git merge` for this reason. If submitting a large code change (other than modules), it's probably a good idea to join ansible-devel and talk about what you would like to do or add first and to avoid duplicate efforts. This not only helps everyone know what's going on, it also helps save time and effort if we decide some changes are needed.
|
||||
* Users list: [ansible-project](http://groups.google.com/group/ansible-project)
|
||||
* Development list: [ansible-devel](http://groups.google.com/group/ansible-devel)
|
||||
* Announcement list: [ansible-announce](http://groups.google.com/group/ansible-announce) - read only
|
||||
* 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](http://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](http://ansible.com)
|
||||
Michael DeHaan -- michael@ansible.com
|
||||
|
||||
[Ansible, Inc](http://ansible.com)
|
||||
|
||||
|
||||
27
RELEASES.txt
27
RELEASES.txt
@ -4,37 +4,14 @@ Ansible Releases at a Glance
|
||||
Active Development
|
||||
++++++++++++++++++
|
||||
|
||||
2.1 "TBD" - in progress
|
||||
1.7 "Summer Nights" -------- CURRENT DEVEL BRANCH
|
||||
|
||||
Released
|
||||
++++++++
|
||||
|
||||
2.0.2 "Over the Hills and Far Away" 04-19-2015
|
||||
2.0.1 "Over the Hills and Far Away" 02-24-2015
|
||||
2.0.0 "Over the Hills and Far Away" 01-12-2015
|
||||
1.9.4 "Dancing In the Streets" 10-09-2015
|
||||
1.9.3 "Dancing In the Streets" 09-03-2015
|
||||
1.9.2 "Dancing In the Streets" 06-24-2015
|
||||
1.9.1 "Dancing In the Streets" 04-27-2015
|
||||
1.9.0 "Dancing In the Streets" 03-25-2015
|
||||
1.8.4 "You Really Got Me" ---- 02-19-2015
|
||||
1.8.3 "You Really Got Me" ---- 02-17-2015
|
||||
1.8.2 "You Really Got Me" ---- 12-04-2014
|
||||
1.8.1 "You Really Got Me" ---- 11-26-2014
|
||||
1.7.2 "Summer Nights" -------- 09-24-2014
|
||||
1.7.1 "Summer Nights" -------- 08-14-2014
|
||||
1.7 "Summer Nights" -------- 08-06-2014
|
||||
1.6.10 "The Cradle Will Rock" - 07-25-2014
|
||||
1.6.9 "The Cradle Will Rock" - 07-24-2014
|
||||
1.6.8 "The Cradle Will Rock" - 07-22-2014
|
||||
1.6.7 "The Cradle Will Rock" - 07-21-2014
|
||||
1.6.6 "The Cradle Will Rock" - 07-01-2014
|
||||
1.6.5 "The Cradle Will Rock" - 06-25-2014
|
||||
1.6.4 "The Cradle Will Rock" - 06-25-2014
|
||||
1.6.3 "The Cradle Will Rock" - 06-09-2014
|
||||
1.6.2 "The Cradle Will Rock" - 05-23-2014
|
||||
1.6.1 "The Cradle Will Rock" - 05-07-2014
|
||||
1.6 "The Cradle Will Rock" - 05-05-2014
|
||||
1.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
|
||||
|
||||
278
bin/ansible
278
bin/ansible
@ -18,98 +18,214 @@
|
||||
# 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 sys
|
||||
import traceback
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleParserError
|
||||
from ansible.utils.display import Display
|
||||
from ansible.utils.unicode import to_unicode
|
||||
from ansible.runner import Runner
|
||||
import ansible.constants as C
|
||||
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")
|
||||
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 options.vault_password_file:
|
||||
this_path = os.path.expanduser(options.vault_password_file)
|
||||
try:
|
||||
f = open(this_path, "rb")
|
||||
tmp_vault_pass=f.read().strip()
|
||||
f.close()
|
||||
except (OSError, IOError), e:
|
||||
raise errors.AnsibleError("Could not read %s: %s" % (this_path, e))
|
||||
|
||||
if not options.ask_vault_pass:
|
||||
vault_pass = tmp_vault_pass
|
||||
|
||||
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")
|
||||
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")
|
||||
|
||||
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:
|
||||
if e.message.endswith(' %s' % sub):
|
||||
raise AnsibleError("Ansible sub-program not implemented: %s" % me)
|
||||
else:
|
||||
raise
|
||||
|
||||
cli = mycli(sys.argv)
|
||||
cli.parse()
|
||||
sys.exit(cli.run())
|
||||
|
||||
except AnsibleOptionsError as e:
|
||||
cli.parser.print_help()
|
||||
display.error(to_unicode(e), wrap_text=False)
|
||||
sys.exit(5)
|
||||
except AnsibleParserError as e:
|
||||
display.error(to_unicode(e), wrap_text=False)
|
||||
sys.exit(4)
|
||||
# TQM takes care of these, but leaving comment to reserve the exit codes
|
||||
# except AnsibleHostUnreachable as e:
|
||||
# display.error(str(e))
|
||||
# sys.exit(3)
|
||||
# except AnsibleHostFailed as e:
|
||||
# display.error(str(e))
|
||||
# sys.exit(2)
|
||||
except AnsibleError as e:
|
||||
display.error(to_unicode(e), wrap_text=False)
|
||||
(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)
|
||||
except KeyboardInterrupt:
|
||||
display.error("User interrupted execution")
|
||||
sys.exit(99)
|
||||
except Exception as e:
|
||||
have_cli_options = cli is not None and cli.options is not None
|
||||
display.error("Unexpected Exception: %s" % to_unicode(e), wrap_text=False)
|
||||
if not have_cli_options or have_cli_options and cli.options.verbosity > 2:
|
||||
display.display(u"the full traceback was:\n\n%s" % to_unicode(traceback.format_exc()))
|
||||
else:
|
||||
display.display("to see the full traceback, use -vvv")
|
||||
sys.exit(250)
|
||||
|
||||
|
||||
@ -1 +0,0 @@
|
||||
ansible
|
||||
291
bin/ansible-doc
Executable file
291
bin/ansible-doc
Executable file
@ -0,0 +1,291 @@
|
||||
#!/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 + ")"
|
||||
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 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)
|
||||
pass
|
||||
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
|
||||
814
bin/ansible-galaxy
Executable file
814
bin/ansible-galaxy
Executable file
@ -0,0 +1,814 @@
|
||||
#!/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()
|
||||
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
|
||||
if member.isreg():
|
||||
member.name = "/".join(member.name.split("/")[1:])
|
||||
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
|
||||
324
bin/ansible-playbook
Executable file
324
bin/ansible-playbook
Executable file
@ -0,0 +1,324 @@
|
||||
#!/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")
|
||||
|
||||
inventory = ansible.inventory.Inventory(options.inventory)
|
||||
inventory.subset(options.subset)
|
||||
if len(inventory.list_hosts()) == 0:
|
||||
raise errors.AnsibleError("provided hosts list is empty")
|
||||
|
||||
sshpass = None
|
||||
sudopass = None
|
||||
su_pass = None
|
||||
vault_pass = None
|
||||
|
||||
if not options.listhosts and not options.syntax and not options.listtasks:
|
||||
options.ask_pass = options.ask_pass or C.DEFAULT_ASK_PASS
|
||||
options.ask_vault_pass = options.ask_vault_pass or C.DEFAULT_ASK_VAULT_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)
|
||||
options.sudo_user = options.sudo_user or C.DEFAULT_SUDO_USER
|
||||
options.su_user = options.su_user or C.DEFAULT_SU_USER
|
||||
|
||||
if options.vault_password_file:
|
||||
this_path = os.path.expanduser(options.vault_password_file)
|
||||
try:
|
||||
f = open(this_path, "rb")
|
||||
tmp_vault_pass=f.read().strip()
|
||||
f.close()
|
||||
except (OSError, IOError), e:
|
||||
raise errors.AnsibleError("Could not read %s: %s" % (this_path, e))
|
||||
|
||||
if not options.ask_vault_pass:
|
||||
vault_pass = tmp_vault_pass
|
||||
|
||||
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)
|
||||
|
||||
# run all playbooks specified on the command line
|
||||
for playbook in args:
|
||||
|
||||
# let inventory know which playbooks are using so it can know the basedirs
|
||||
inventory.set_playbook_basedir(os.path.dirname(playbook))
|
||||
|
||||
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)
|
||||
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
|
||||
230
bin/ansible-pull
Executable file
230
bin/ansible-pull
Executable file
@ -0,0 +1,230 @@
|
||||
#!/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")
|
||||
|
||||
inv_opts = 'localhost,'
|
||||
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
|
||||
219
bin/ansible-vault
Executable file
219
bin/ansible-vault
Executable file
@ -0,0 +1,219 @@
|
||||
#!/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
|
||||
|
||||
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")
|
||||
|
||||
# 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 _read_password(filename):
|
||||
f = open(filename, "rb")
|
||||
data = f.read()
|
||||
f.close
|
||||
data = data.strip()
|
||||
return data
|
||||
|
||||
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 = _read_password(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 = _read_password(options.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 = _read_password(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 = _read_password(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 = _read_password(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()
|
||||
|
||||
# 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 infromation 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,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)
|
||||
print(json.dumps(data, indent=2))
|
||||
|
||||
elif options.list:
|
||||
data = self.get_list()
|
||||
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,55 +0,0 @@
|
||||
# Ansible Collins external inventory script settings
|
||||
#
|
||||
|
||||
[collins]
|
||||
|
||||
host = http://localhost:9000
|
||||
|
||||
username = blake
|
||||
password = admin:first
|
||||
|
||||
# Specifies a timeout for all HTTP requests to Collins.
|
||||
timeout_secs = 120
|
||||
|
||||
# Specifies a maximum number of retries per Collins request.
|
||||
max_retries = 5
|
||||
|
||||
# Specifies the number of results to return per paginated query as specified in
|
||||
# the Pagination section of the Collins API docs:
|
||||
# http://tumblr.github.io/collins/api.html
|
||||
results_per_query = 100
|
||||
|
||||
# Specifies the Collins asset type which will be queried for; most typically
|
||||
# you'll want to leave this at the default of SERVER_NODE.
|
||||
asset_type = SERVER_NODE
|
||||
|
||||
# Collins assets can optionally be assigned hostnames; this option will preference
|
||||
# the selection of an asset's hostname over an IP address as the primary identifier
|
||||
# in the Ansible inventory. Typically, this value should be set to true if assets
|
||||
# are assigned hostnames.
|
||||
prefer_hostnames = true
|
||||
|
||||
# Within Collins, assets can be granted multiple IP addresses; this configuration
|
||||
# value specifies the index within the 'ADDRESSES' array as returned by the
|
||||
# following API endpoint:
|
||||
# http://tumblr.github.io/collins/api.html#api-ipam-asset-addresses-section
|
||||
ip_address_index = 0
|
||||
|
||||
# Sets whether Collins instances in multiple datacenters will be queried.
|
||||
query_remote_dcs = false
|
||||
|
||||
# API calls to Collins can involve large, substantial queries. 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-collins.cache
|
||||
# - ansible-collins.index
|
||||
cache_path = /tmp
|
||||
|
||||
# If errors occur while querying inventory, logging messages will be written
|
||||
# to a logfile in the specified directory:
|
||||
# - ansible-collins.log
|
||||
log_path = /tmp
|
||||
|
||||
# The number of seconds that a cache file is considered valid. After this many
|
||||
# seconds, a new API call will be made, and the cache file will be updated.
|
||||
cache_max_age = 600
|
||||
@ -1,452 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Collins 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.
|
||||
|
||||
Collins is a hardware asset management system originally developed by
|
||||
Tumblr for tracking new hardware as it built out its own datacenters. It
|
||||
exposes a rich API for manipulating and querying one's hardware inventory,
|
||||
which makes it an ideal 'single point of truth' for driving systems
|
||||
automation like Ansible. Extensive documentation on Collins, including a quickstart,
|
||||
API docs, and a full reference manual, can be found here:
|
||||
|
||||
http://tumblr.github.io/collins
|
||||
|
||||
This script adds support to Ansible for obtaining a dynamic inventory of
|
||||
assets in your infrastructure, grouping them in Ansible by their useful attributes,
|
||||
and binding all facts provided by Collins to each host so that they can be used to
|
||||
drive automation. Some parts of this script were cribbed shamelessly from mdehaan's
|
||||
Cobbler inventory script.
|
||||
|
||||
To use it, copy it to your repo and pass -i <collins script> to the ansible or
|
||||
ansible-playbook command; if you'd like to use it by default, simply copy collins.ini
|
||||
to /etc/ansible and this script to /etc/ansible/hosts.
|
||||
|
||||
Alongside the options set in collins.ini, there are several environment variables
|
||||
that will be used instead of the configured values if they are set:
|
||||
|
||||
- COLLINS_USERNAME - specifies a username to use for Collins authentication
|
||||
- COLLINS_PASSWORD - specifies a password to use for Collins authentication
|
||||
- COLLINS_ASSET_TYPE - specifies a Collins asset type to use during querying;
|
||||
this can be used to run Ansible automation against different asset classes than
|
||||
server nodes, such as network switches and PDUs
|
||||
- COLLINS_CONFIG - specifies an alternative location for collins.ini, defaults to
|
||||
<location of collins.py>/collins.ini
|
||||
|
||||
If errors are encountered during operation, this script will return an exit code of
|
||||
255; otherwise, it will return an exit code of 0.
|
||||
|
||||
Collins attributes are accessible as variables in ansible via the COLLINS['attribute_name'].
|
||||
|
||||
Tested against Ansible 1.8.2 and Collins 1.3.0.
|
||||
"""
|
||||
|
||||
# (c) 2014, Steve Salevan <steve.salevan@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 argparse
|
||||
import ConfigParser
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from time import time
|
||||
import traceback
|
||||
import urllib
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
from six import iteritems
|
||||
|
||||
from ansible.module_utils.urls import open_url
|
||||
|
||||
class CollinsDefaults(object):
|
||||
ASSETS_API_ENDPOINT = '%s/api/assets'
|
||||
SPECIAL_ATTRIBUTES = set([
|
||||
'CREATED',
|
||||
'DELETED',
|
||||
'UPDATED',
|
||||
'STATE',
|
||||
])
|
||||
LOG_FORMAT = '%(asctime)-15s %(message)s'
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MaxRetriesError(Error):
|
||||
pass
|
||||
|
||||
|
||||
class CollinsInventory(object):
|
||||
|
||||
def __init__(self):
|
||||
""" Constructs CollinsInventory object and reads all configuration. """
|
||||
|
||||
self.inventory = dict() # A list of groups and the hosts in that group
|
||||
self.cache = dict() # Details about hosts in the inventory
|
||||
|
||||
# Read settings and parse CLI arguments
|
||||
self.read_settings()
|
||||
self.parse_cli_args()
|
||||
|
||||
logging.basicConfig(format=CollinsDefaults.LOG_FORMAT,
|
||||
filename=self.log_location)
|
||||
self.log = logging.getLogger('CollinsInventory')
|
||||
|
||||
def _asset_get_attribute(self, asset, attrib):
|
||||
""" Returns a user-defined attribute from an asset if it exists; otherwise,
|
||||
returns None. """
|
||||
|
||||
if 'ATTRIBS' in asset:
|
||||
for attrib_block in asset['ATTRIBS'].keys():
|
||||
if attrib in asset['ATTRIBS'][attrib_block]:
|
||||
return asset['ATTRIBS'][attrib_block][attrib]
|
||||
return None
|
||||
|
||||
def _asset_has_attribute(self, asset, attrib):
|
||||
""" Returns whether a user-defined attribute is present on an asset. """
|
||||
|
||||
if 'ATTRIBS' in asset:
|
||||
for attrib_block in asset['ATTRIBS'].keys():
|
||||
if attrib in asset['ATTRIBS'][attrib_block]:
|
||||
return True
|
||||
return False
|
||||
|
||||
def run(self):
|
||||
""" Main execution path """
|
||||
|
||||
# Updates cache if cache is not present or has expired.
|
||||
successful = True
|
||||
if self.args.refresh_cache:
|
||||
successful = self.update_cache()
|
||||
elif not self.is_cache_valid():
|
||||
successful = self.update_cache()
|
||||
else:
|
||||
successful = self.load_inventory_from_cache()
|
||||
successful &= self.load_cache_from_cache()
|
||||
|
||||
data_to_print = ""
|
||||
|
||||
# Data to print
|
||||
if self.args.host:
|
||||
data_to_print = self.get_host_info()
|
||||
|
||||
elif self.args.list:
|
||||
# Display list of instances for inventory
|
||||
data_to_print = self.json_format_dict(self.inventory, self.args.pretty)
|
||||
|
||||
else: # default action with no options
|
||||
data_to_print = self.json_format_dict(self.inventory, self.args.pretty)
|
||||
|
||||
print(data_to_print)
|
||||
return successful
|
||||
|
||||
def find_assets(self, attributes = {}, operation = 'AND'):
|
||||
""" Obtains Collins assets matching the provided attributes. """
|
||||
|
||||
# Formats asset search query to locate assets matching attributes, using
|
||||
# the CQL search feature as described here:
|
||||
# http://tumblr.github.io/collins/recipes.html
|
||||
attributes_query = [ '='.join(attr_pair)
|
||||
for attr_pair in iteritems(attributes) ]
|
||||
query_parameters = {
|
||||
'details': ['True'],
|
||||
'operation': [operation],
|
||||
'query': attributes_query,
|
||||
'remoteLookup': [str(self.query_remote_dcs)],
|
||||
'size': [self.results_per_query],
|
||||
'type': [self.collins_asset_type],
|
||||
}
|
||||
assets = []
|
||||
cur_page = 0
|
||||
num_retries = 0
|
||||
# Locates all assets matching the provided query, exhausting pagination.
|
||||
while True:
|
||||
if num_retries == self.collins_max_retries:
|
||||
raise MaxRetriesError("Maximum of %s retries reached; giving up" % \
|
||||
self.collins_max_retries)
|
||||
query_parameters['page'] = cur_page
|
||||
query_url = "%s?%s" % (
|
||||
(CollinsDefaults.ASSETS_API_ENDPOINT % self.collins_host),
|
||||
urllib.urlencode(query_parameters, doseq=True)
|
||||
)
|
||||
try:
|
||||
response = open_url(query_url,
|
||||
timeout=self.collins_timeout_secs,
|
||||
url_username=self.collins_username,
|
||||
url_password=self.collins_password)
|
||||
json_response = json.loads(response.read())
|
||||
# Adds any assets found to the array of assets.
|
||||
assets += json_response['data']['Data']
|
||||
# If we've retrieved all of our assets, breaks out of the loop.
|
||||
if len(json_response['data']['Data']) == 0:
|
||||
break
|
||||
cur_page += 1
|
||||
num_retries = 0
|
||||
except:
|
||||
self.log.error("Error while communicating with Collins, retrying:\n%s",
|
||||
traceback.format_exc())
|
||||
num_retries += 1
|
||||
return assets
|
||||
|
||||
def is_cache_valid(self):
|
||||
""" Determines if the cache files have expired, or if it is still valid """
|
||||
|
||||
if os.path.isfile(self.cache_path_cache):
|
||||
mod_time = os.path.getmtime(self.cache_path_cache)
|
||||
current_time = time()
|
||||
if (mod_time + self.cache_max_age) > current_time:
|
||||
if os.path.isfile(self.cache_path_inventory):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def read_settings(self):
|
||||
""" Reads the settings from the collins.ini file """
|
||||
|
||||
config_loc = os.getenv('COLLINS_CONFIG',
|
||||
os.path.dirname(os.path.realpath(__file__)) + '/collins.ini')
|
||||
|
||||
config = ConfigParser.SafeConfigParser()
|
||||
config.read(os.path.dirname(os.path.realpath(__file__)) + '/collins.ini')
|
||||
|
||||
self.collins_host = config.get('collins', 'host')
|
||||
self.collins_username = os.getenv('COLLINS_USERNAME',
|
||||
config.get('collins', 'username'))
|
||||
self.collins_password = os.getenv('COLLINS_PASSWORD',
|
||||
config.get('collins', 'password'))
|
||||
self.collins_asset_type = os.getenv('COLLINS_ASSET_TYPE',
|
||||
config.get('collins', 'asset_type'))
|
||||
self.collins_timeout_secs = config.getint('collins', 'timeout_secs')
|
||||
self.collins_max_retries = config.getint('collins', 'max_retries')
|
||||
|
||||
self.results_per_query = config.getint('collins', 'results_per_query')
|
||||
self.ip_address_index = config.getint('collins', 'ip_address_index')
|
||||
self.query_remote_dcs = config.getboolean('collins', 'query_remote_dcs')
|
||||
self.prefer_hostnames = config.getboolean('collins', 'prefer_hostnames')
|
||||
|
||||
cache_path = config.get('collins', 'cache_path')
|
||||
self.cache_path_cache = cache_path + \
|
||||
'/ansible-collins-%s.cache' % self.collins_asset_type
|
||||
self.cache_path_inventory = cache_path + \
|
||||
'/ansible-collins-%s.index' % self.collins_asset_type
|
||||
self.cache_max_age = config.getint('collins', 'cache_max_age')
|
||||
|
||||
log_path = config.get('collins', 'log_path')
|
||||
self.log_location = log_path + '/ansible-collins.log'
|
||||
|
||||
def parse_cli_args(self):
|
||||
""" Command line argument processing """
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Produces an Ansible Inventory file based on Collins')
|
||||
parser.add_argument('--list',
|
||||
action='store_true', default=True, help='List instances (default: True)')
|
||||
parser.add_argument('--host',
|
||||
action='store', help='Get all the variables about a specific instance')
|
||||
parser.add_argument('--refresh-cache',
|
||||
action='store_true', default=False,
|
||||
help='Force refresh of cache by making API requests to Collins ' \
|
||||
'(default: False - use cache files)')
|
||||
parser.add_argument('--pretty',
|
||||
action='store_true', default=False, help='Pretty print all JSON output')
|
||||
self.args = parser.parse_args()
|
||||
|
||||
def update_cache(self):
|
||||
""" Make calls to Collins and saves the output in a cache """
|
||||
|
||||
self.cache = dict()
|
||||
self.inventory = dict()
|
||||
|
||||
# Locates all server assets from Collins.
|
||||
try:
|
||||
server_assets = self.find_assets()
|
||||
except:
|
||||
self.log.error("Error while locating assets from Collins:\n%s",
|
||||
traceback.format_exc())
|
||||
return False
|
||||
|
||||
for asset in server_assets:
|
||||
# Determines the index to retrieve the asset's IP address either by an
|
||||
# attribute set on the Collins asset or the pre-configured value.
|
||||
if self._asset_has_attribute(asset, 'ANSIBLE_IP_INDEX'):
|
||||
ip_index = self._asset_get_attribute(asset, 'ANSIBLE_IP_INDEX')
|
||||
try:
|
||||
ip_index = int(ip_index)
|
||||
except:
|
||||
self.log.error(
|
||||
"ANSIBLE_IP_INDEX attribute on asset %s not an integer: %s", asset,
|
||||
ip_index)
|
||||
else:
|
||||
ip_index = self.ip_address_index
|
||||
|
||||
asset['COLLINS'] = {}
|
||||
|
||||
# Attempts to locate the asset's primary identifier (hostname or IP address),
|
||||
# which will be used to index the asset throughout the Ansible inventory.
|
||||
if self.prefer_hostnames and self._asset_has_attribute(asset, 'HOSTNAME'):
|
||||
asset_identifier = self._asset_get_attribute(asset, 'HOSTNAME')
|
||||
elif 'ADDRESSES' not in asset:
|
||||
self.log.warning("No IP addresses found for asset '%s', skipping",
|
||||
asset)
|
||||
continue
|
||||
elif len(asset['ADDRESSES']) < ip_index + 1:
|
||||
self.log.warning(
|
||||
"No IP address found at index %s for asset '%s', skipping",
|
||||
ip_index, asset)
|
||||
continue
|
||||
else:
|
||||
asset_identifier = asset['ADDRESSES'][ip_index]['ADDRESS']
|
||||
|
||||
# Adds an asset index to the Ansible inventory based upon unpacking
|
||||
# the name of the asset's current STATE from its dictionary.
|
||||
if 'STATE' in asset['ASSET'] and asset['ASSET']['STATE']:
|
||||
state_inventory_key = self.to_safe(
|
||||
'STATE-%s' % asset['ASSET']['STATE']['NAME'])
|
||||
self.push(self.inventory, state_inventory_key, asset_identifier)
|
||||
|
||||
# Indexes asset by all user-defined Collins attributes.
|
||||
if 'ATTRIBS' in asset:
|
||||
for attrib_block in asset['ATTRIBS'].keys():
|
||||
for attrib in asset['ATTRIBS'][attrib_block].keys():
|
||||
asset['COLLINS'][attrib] = asset['ATTRIBS'][attrib_block][attrib]
|
||||
attrib_key = self.to_safe('%s-%s' % (attrib, asset['ATTRIBS'][attrib_block][attrib]))
|
||||
self.push(self.inventory, attrib_key, asset_identifier)
|
||||
|
||||
# Indexes asset by all built-in Collins attributes.
|
||||
for attribute in asset['ASSET'].keys():
|
||||
if attribute not in CollinsDefaults.SPECIAL_ATTRIBUTES:
|
||||
attribute_val = asset['ASSET'][attribute]
|
||||
if attribute_val is not None:
|
||||
attrib_key = self.to_safe('%s-%s' % (attribute, attribute_val))
|
||||
self.push(self.inventory, attrib_key, asset_identifier)
|
||||
|
||||
# Indexes asset by hardware product information.
|
||||
if 'HARDWARE' in asset:
|
||||
if 'PRODUCT' in asset['HARDWARE']['BASE']:
|
||||
product = asset['HARDWARE']['BASE']['PRODUCT']
|
||||
if product:
|
||||
product_key = self.to_safe(
|
||||
'HARDWARE-PRODUCT-%s' % asset['HARDWARE']['BASE']['PRODUCT'])
|
||||
self.push(self.inventory, product_key, asset_identifier)
|
||||
|
||||
# Indexing now complete, adds the host details to the asset cache.
|
||||
self.cache[asset_identifier] = asset
|
||||
|
||||
try:
|
||||
self.write_to_cache(self.cache, self.cache_path_cache)
|
||||
self.write_to_cache(self.inventory, self.cache_path_inventory)
|
||||
except:
|
||||
self.log.error("Error while writing to cache:\n%s", traceback.format_exc())
|
||||
return False
|
||||
return True
|
||||
|
||||
def push(self, dictionary, key, value):
|
||||
""" Adds a value to a list at a dictionary key, creating the list if it doesn't
|
||||
exist. """
|
||||
|
||||
if key not in dictionary:
|
||||
dictionary[key] = []
|
||||
dictionary[key].append(value)
|
||||
|
||||
def get_host_info(self):
|
||||
""" Get variables about a specific host. """
|
||||
|
||||
if not self.cache or len(self.cache) == 0:
|
||||
# Need to load index from cache
|
||||
self.load_cache_from_cache()
|
||||
|
||||
if not self.args.host in self.cache:
|
||||
# try updating the cache
|
||||
self.update_cache()
|
||||
|
||||
if not self.args.host in self.cache:
|
||||
# host might not exist anymore
|
||||
return self.json_format_dict({}, self.args.pretty)
|
||||
|
||||
return self.json_format_dict(self.cache[self.args.host], self.args.pretty)
|
||||
|
||||
def load_inventory_from_cache(self):
|
||||
""" Reads the index from the cache file sets self.index """
|
||||
|
||||
try:
|
||||
cache = open(self.cache_path_inventory, 'r')
|
||||
json_inventory = cache.read()
|
||||
self.inventory = json.loads(json_inventory)
|
||||
return True
|
||||
except:
|
||||
self.log.error("Error while loading inventory:\n%s",
|
||||
traceback.format_exc())
|
||||
self.inventory = {}
|
||||
return False
|
||||
|
||||
def load_cache_from_cache(self):
|
||||
""" Reads the cache from the cache file sets self.cache """
|
||||
|
||||
try:
|
||||
cache = open(self.cache_path_cache, 'r')
|
||||
json_cache = cache.read()
|
||||
self.cache = json.loads(json_cache)
|
||||
return True
|
||||
except:
|
||||
self.log.error("Error while loading host cache:\n%s",
|
||||
traceback.format_exc())
|
||||
self.cache = {}
|
||||
return False
|
||||
|
||||
def write_to_cache(self, data, filename):
|
||||
""" Writes data in JSON format to a specified file. """
|
||||
|
||||
json_data = self.json_format_dict(data, self.args.pretty)
|
||||
cache = open(filename, 'w')
|
||||
cache.write(json_data)
|
||||
cache.close()
|
||||
|
||||
def to_safe(self, word):
|
||||
""" Converts 'bad' characters in a string to underscores so they
|
||||
can be used as Ansible groups """
|
||||
|
||||
return re.sub("[^A-Za-z0-9\-]", "_", word)
|
||||
|
||||
def json_format_dict(self, data, pretty=False):
|
||||
""" Converts a dict to a JSON object and dumps it as a formatted string """
|
||||
|
||||
if pretty:
|
||||
return json.dumps(data, sort_keys=True, indent=2)
|
||||
else:
|
||||
return json.dumps(data)
|
||||
|
||||
|
||||
if __name__ in '__main__':
|
||||
inventory = CollinsInventory()
|
||||
if inventory.run():
|
||||
sys.exit(0)
|
||||
else:
|
||||
sys.exit(-1)
|
||||
@ -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,429 +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
|
||||
'nyc-web-1' and kv_groups = 'ansible/groups' then the key
|
||||
'v1/kv/ansible/groups/nyc-web-1' will be queried for a group list. If this query
|
||||
returned 'test,honeypot' then the node address to both groups.
|
||||
|
||||
'kv_metadata':
|
||||
|
||||
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.
|
||||
|
||||
'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
|
||||
from time import time
|
||||
import sys
|
||||
import ConfigParser
|
||||
import urllib, urllib2, base64
|
||||
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
try:
|
||||
import consul
|
||||
except ImportError as e:
|
||||
print("""failed=True msg='python-consul required for this module. see
|
||||
http://python-consul.readthedocs.org/en/latest/#installation'""")
|
||||
sys.exit(1)
|
||||
|
||||
from six import iteritems
|
||||
|
||||
|
||||
class ConsulInventory(object):
|
||||
|
||||
def __init__(self):
|
||||
''' Create an inventory based on the catalog of nodes and services
|
||||
registered in a consul cluster'''
|
||||
self.node_metadata = {}
|
||||
self.nodes = {}
|
||||
self.nodes_by_service = {}
|
||||
self.nodes_by_tag = {}
|
||||
self.nodes_by_datacenter = {}
|
||||
self.nodes_by_kv = {}
|
||||
self.nodes_by_availability = {}
|
||||
self.current_dc = None
|
||||
|
||||
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,152 +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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
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,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,230 +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
|
||||
|
||||
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 addresses.itervalues():
|
||||
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:
|
||||
print("usage: --list ..OR.. --host <hostname>")
|
||||
sys.exit(1)
|
||||
@ -1,341 +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
|
||||
|
||||
|
||||
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.iteritems():
|
||||
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, basestring)
|
||||
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,229 +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.
|
||||
#
|
||||
# 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
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
try:
|
||||
import json
|
||||
except:
|
||||
import simplejson as json
|
||||
|
||||
import os_client_config
|
||||
import shade
|
||||
import shade.inventory
|
||||
|
||||
CONFIG_FILES = ['/etc/ansible/openstack.yaml']
|
||||
|
||||
|
||||
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)
|
||||
|
||||
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 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']
|
||||
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:
|
||||
server = servers[0]
|
||||
hostvars[name] = dict(
|
||||
ansible_ssh_host=server['interface_ip'],
|
||||
openstack=server)
|
||||
for group in get_groups_from_server(server, namegroup=False):
|
||||
groups[group].append(server['name'])
|
||||
else:
|
||||
for server in servers:
|
||||
server_id = server['id']
|
||||
hostvars[server_id] = dict(
|
||||
ansible_ssh_host=server['interface_ip'],
|
||||
openstack=server)
|
||||
for group in get_groups_from_server(server, namegroup=True):
|
||||
groups[group].append(server_id)
|
||||
groups['_meta'] = {'hostvars': hostvars}
|
||||
return groups
|
||||
|
||||
|
||||
def is_cache_stale(cache_file, cache_expiration_time, refresh=False):
|
||||
''' Determines if cache file has expired, or if it is still valid '''
|
||||
if refresh:
|
||||
return True
|
||||
if os.path.isfile(cache_file):
|
||||
mod_time = os.path.getmtime(cache_file)
|
||||
current_time = time.time()
|
||||
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,
|
||||
}
|
||||
))
|
||||
|
||||
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,31 +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
|
||||
@ -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,287 +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['username'] = os.environ.get('OVIRT_EMAIL')
|
||||
kwargs['password'] = os.environ.get('OVIRT_PASS')
|
||||
|
||||
# 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': inst.get_instance_type(),
|
||||
'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()]
|
||||
|
||||
# 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,180 +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/>.
|
||||
|
||||
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 ProxmoxQemu(dict):
|
||||
def get_variables(self):
|
||||
variables = {}
|
||||
for key, value in iteritems(self):
|
||||
variables['proxmox_' + key] = value
|
||||
return variables
|
||||
|
||||
class ProxmoxQemuList(list):
|
||||
def __init__(self, data=[]):
|
||||
for item in data:
|
||||
self.append(ProxmoxQemu(item))
|
||||
|
||||
def get_names(self):
|
||||
return [qemu['name'] for qemu in self if qemu['template'] != 1]
|
||||
|
||||
def get_by_name(self, name):
|
||||
results = [qemu for qemu in self if qemu['name'] == name]
|
||||
return results[0] if len(results) > 0 else None
|
||||
|
||||
def get_variables(self):
|
||||
variables = {}
|
||||
for qemu in self:
|
||||
variables[qemu['name']] = qemu.get_variables()
|
||||
|
||||
return variables
|
||||
|
||||
class ProxmoxPoolList(list):
|
||||
def get_names(self):
|
||||
return [pool['poolid'] for pool in self]
|
||||
|
||||
class ProxmoxPool(dict):
|
||||
def get_members_name(self):
|
||||
return [member['name'] for member in self['members'] if member['template'] != 1]
|
||||
|
||||
class ProxmoxAPI(object):
|
||||
def __init__(self, options):
|
||||
self.options = options
|
||||
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 node_qemu(self, node):
|
||||
return ProxmoxQemuList(self.get('api2/json/nodes/{}/qemu'.format(node)))
|
||||
|
||||
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())
|
||||
|
||||
# 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,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,455 +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:
|
||||
print('pyrax is required for this module')
|
||||
sys.exit(1)
|
||||
|
||||
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.stderr.write('No value in environment variable %s and/or no '
|
||||
'credentials file at %s\n'
|
||||
% ('RAX_CREDS_FILE', default_creds_file))
|
||||
sys.exit(1)
|
||||
|
||||
identity_type = pyrax.get_setting('identity_type')
|
||||
pyrax.set_setting('identity_type', identity_type or 'rackspace')
|
||||
|
||||
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.stderr.write("%s: %s\n" % (e, e.message))
|
||||
sys.exit(1)
|
||||
|
||||
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.stderr.write('Unsupported region %s' % region)
|
||||
sys.exit(1)
|
||||
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,163 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
SoftLayer external inventory script.
|
||||
|
||||
The SoftLayer Python API client is required. Use `pip install softlayer` to install it.
|
||||
You have a few different options for configuring your username and api_key. You can pass
|
||||
environment variables (SL_USERNAME and SL_API_KEY). You can also write INI file to
|
||||
~/.softlayer or /etc/softlayer.conf. For more information see the SL API at:
|
||||
- https://softlayer-python.readthedocs.org/en/latest/config_file.html
|
||||
|
||||
The SoftLayer Python client has a built in command for saving this configuration file
|
||||
via the command `sl config setup`.
|
||||
"""
|
||||
|
||||
# Copyright (C) 2014 AJ Bourg <aj@ajbourg.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/>.
|
||||
|
||||
#
|
||||
# I found the structure of the ec2.py script very helpful as an example
|
||||
# as I put this together. Thanks to whoever wrote that script!
|
||||
#
|
||||
|
||||
import SoftLayer
|
||||
import re
|
||||
import argparse
|
||||
try:
|
||||
import json
|
||||
except:
|
||||
import simplejson as json
|
||||
|
||||
class SoftLayerInventory(object):
|
||||
def _empty_inventory(self):
|
||||
return {"_meta" : {"hostvars" : {}}}
|
||||
|
||||
def __init__(self):
|
||||
'''Main path'''
|
||||
|
||||
self.inventory = self._empty_inventory()
|
||||
|
||||
self.parse_options()
|
||||
|
||||
if self.args.list:
|
||||
self.get_all_servers()
|
||||
print(self.json_format_dict(self.inventory, True))
|
||||
elif self.args.host:
|
||||
self.get_virtual_servers()
|
||||
print(self.json_format_dict(self.inventory["_meta"]["hostvars"][self.args.host], True))
|
||||
|
||||
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 push(self, my_dict, key, element):
|
||||
'''Push an element onto an array that may not have been defined in the dict'''
|
||||
|
||||
if key in my_dict:
|
||||
my_dict[key].append(element);
|
||||
else:
|
||||
my_dict[key] = [element]
|
||||
|
||||
def parse_options(self):
|
||||
'''Parse all the arguments from the CLI'''
|
||||
|
||||
parser = argparse.ArgumentParser(description='Produce an Ansible Inventory file based on SoftLayer')
|
||||
parser.add_argument('--list', action='store_true', default=False,
|
||||
help='List instances (default: False)')
|
||||
parser.add_argument('--host', action='store',
|
||||
help='Get all the variables about a specific instance')
|
||||
self.args = parser.parse_args()
|
||||
|
||||
def json_format_dict(self, data, pretty=False):
|
||||
'''Converts a dict to a JSON object and dumps it as a formatted string'''
|
||||
|
||||
if pretty:
|
||||
return json.dumps(data, sort_keys=True, indent=2)
|
||||
else:
|
||||
return json.dumps(data)
|
||||
|
||||
def process_instance(self, instance, instance_type="virtual"):
|
||||
'''Populate the inventory dictionary with any instance information'''
|
||||
|
||||
# only want active instances
|
||||
if 'status' in instance and instance['status']['name'] != 'Active':
|
||||
return
|
||||
|
||||
# and powered on instances
|
||||
if 'powerState' in instance and instance['powerState']['name'] != 'Running':
|
||||
return
|
||||
|
||||
# 5 is active for hardware... see https://forums.softlayer.com/forum/softlayer-developer-network/general-discussion/2955-hardwarestatusid
|
||||
if 'hardwareStatusId' in instance and instance['hardwareStatusId'] != 5:
|
||||
return
|
||||
|
||||
# if there's no IP address, we can't reach it
|
||||
if 'primaryIpAddress' not in instance:
|
||||
return
|
||||
|
||||
dest = instance['primaryIpAddress']
|
||||
|
||||
self.inventory["_meta"]["hostvars"][dest] = instance
|
||||
|
||||
# Inventory: group by memory
|
||||
if 'maxMemory' in instance:
|
||||
self.push(self.inventory, self.to_safe('memory_' + str(instance['maxMemory'])), dest)
|
||||
elif 'memoryCapacity' in instance:
|
||||
self.push(self.inventory, self.to_safe('memory_' + str(instance['memoryCapacity'])), dest)
|
||||
|
||||
# Inventory: group by cpu count
|
||||
if 'maxCpu' in instance:
|
||||
self.push(self.inventory, self.to_safe('cpu_' + str(instance['maxCpu'])), dest)
|
||||
elif 'processorPhysicalCoreAmount' in instance:
|
||||
self.push(self.inventory, self.to_safe('cpu_' + str(instance['processorPhysicalCoreAmount'])), dest)
|
||||
|
||||
# Inventory: group by datacenter
|
||||
self.push(self.inventory, self.to_safe('datacenter_' + instance['datacenter']['name']), dest)
|
||||
|
||||
# Inventory: group by hostname
|
||||
self.push(self.inventory, self.to_safe(instance['hostname']), dest)
|
||||
|
||||
# Inventory: group by FQDN
|
||||
self.push(self.inventory, self.to_safe(instance['fullyQualifiedDomainName']), dest)
|
||||
|
||||
# Inventory: group by domain
|
||||
self.push(self.inventory, self.to_safe(instance['domain']), dest)
|
||||
|
||||
# Inventory: group by type (hardware/virtual)
|
||||
self.push(self.inventory, instance_type, dest)
|
||||
|
||||
def get_virtual_servers(self):
|
||||
'''Get all the CCI instances'''
|
||||
vs = SoftLayer.VSManager(self.client)
|
||||
instances = vs.list_instances()
|
||||
|
||||
for instance in instances:
|
||||
self.process_instance(instance)
|
||||
|
||||
def get_physical_servers(self):
|
||||
'''Get all the hardware instances'''
|
||||
hw = SoftLayer.HardwareManager(self.client)
|
||||
instances = hw.list_hardware()
|
||||
|
||||
for instance in instances:
|
||||
self.process_instance(instance, 'hardware')
|
||||
|
||||
def get_all_servers(self):
|
||||
self.client = SoftLayer.Client()
|
||||
self.get_virtual_servers()
|
||||
self.get_physical_servers()
|
||||
|
||||
SoftLayerInventory()
|
||||
@ -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, 2775)
|
||||
|
||||
# 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,43 +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_
|
||||
|
||||
[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
|
||||
@ -1,436 +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 sys
|
||||
import time
|
||||
import ConfigParser
|
||||
|
||||
from six import text_type
|
||||
|
||||
# Disable logging message trigged by pSphere/suds.
|
||||
try:
|
||||
from logging import NullHandler
|
||||
except ImportError:
|
||||
from logging import Handler
|
||||
class NullHandler(Handler):
|
||||
def emit(self, record):
|
||||
pass
|
||||
logging.getLogger('psphere').addHandler(NullHandler())
|
||||
logging.getLogger('suds').addHandler(NullHandler())
|
||||
|
||||
from psphere.client import Client
|
||||
from psphere.errors import ObjectNotFoundError
|
||||
from psphere.managedobjects import HostSystem, VirtualMachine, ManagedObject, Network
|
||||
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')
|
||||
|
||||
# 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, basestring) 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, basestring)):
|
||||
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
|
||||
|
||||
# Loop through physical hosts:
|
||||
for host in HostSystem.all(self.client):
|
||||
|
||||
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,26 +0,0 @@
|
||||
# Ansible Windows Azure external inventory script settings
|
||||
#
|
||||
|
||||
[azure]
|
||||
|
||||
# The module needs your Windows Azure subscription ID and Management certificate path.
|
||||
# These may also be specified on the command line via --subscription-id and --cert-path
|
||||
# or via the environment variables AZURE_SUBSCRIPTION_ID and AZURE_CERT_PATH
|
||||
#
|
||||
#subscription_id = aaaaaaaa-1234-1234-1234-aaaaaaaaaaaa
|
||||
#cert_path = /path/to/cert.pem
|
||||
|
||||
|
||||
# API calls to Windows Azure may be slow. For this reason, we cache the results
|
||||
# of an API call. Set this to the path you want cache files to be written to.
|
||||
# Two files will be written to this directory:
|
||||
# - ansible-azure.cache
|
||||
# - ansible-azure.index
|
||||
#
|
||||
cache_path = /tmp
|
||||
|
||||
|
||||
# The number of seconds a cache file is considered valid. After this many
|
||||
# seconds, a new API call will be made, and the cache file will be updated.
|
||||
#
|
||||
cache_max_age = 300
|
||||
@ -1,294 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
'''
|
||||
Windows Azure external inventory script
|
||||
=======================================
|
||||
|
||||
Generates inventory that Ansible can understand by making API request to
|
||||
Windows Azure using the azure python library.
|
||||
|
||||
NOTE: This script assumes Ansible is being executed where azure is already
|
||||
installed.
|
||||
|
||||
pip install azure
|
||||
|
||||
Adapted from the ansible Linode plugin by Dan Slimmon.
|
||||
'''
|
||||
|
||||
# (c) 2013, John Whitbeck
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
######################################################################
|
||||
|
||||
# Standard imports
|
||||
import re
|
||||
import sys
|
||||
import argparse
|
||||
import os
|
||||
from urlparse import urlparse
|
||||
from time import time
|
||||
try:
|
||||
import json
|
||||
except ImportError:
|
||||
import simplejson as json
|
||||
|
||||
try:
|
||||
import azure
|
||||
from azure import WindowsAzureError
|
||||
from azure.servicemanagement import ServiceManagementService
|
||||
except ImportError as e:
|
||||
print("failed=True msg='`azure` library required for this script'")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# Imports for ansible
|
||||
import ConfigParser
|
||||
|
||||
class AzureInventory(object):
|
||||
def __init__(self):
|
||||
"""Main execution path."""
|
||||
# Inventory grouped by display group
|
||||
self.inventory = {}
|
||||
# Index of deployment name -> host
|
||||
self.index = {}
|
||||
self.host_metadata = {}
|
||||
|
||||
# Cache setting defaults.
|
||||
# These can be overridden in settings (see `read_settings`).
|
||||
cache_dir = os.path.expanduser('~')
|
||||
self.cache_path_cache = os.path.join(cache_dir, '.ansible-azure.cache')
|
||||
self.cache_path_index = os.path.join(cache_dir, '.ansible-azure.index')
|
||||
self.cache_max_age = 0
|
||||
|
||||
# Read settings and parse CLI arguments
|
||||
self.read_settings()
|
||||
self.read_environment()
|
||||
self.parse_cli_args()
|
||||
|
||||
# Initialize Azure ServiceManagementService
|
||||
self.sms = ServiceManagementService(self.subscription_id, self.cert_path)
|
||||
|
||||
# Cache
|
||||
if self.args.refresh_cache:
|
||||
self.do_api_calls_update_cache()
|
||||
elif not self.is_cache_valid():
|
||||
self.do_api_calls_update_cache()
|
||||
|
||||
if self.args.list_images:
|
||||
data_to_print = self.json_format_dict(self.get_images(), True)
|
||||
elif self.args.list or self.args.host:
|
||||
# Display list of nodes for inventory
|
||||
if len(self.inventory) == 0:
|
||||
data = json.loads(self.get_inventory_from_cache())
|
||||
else:
|
||||
data = self.inventory
|
||||
|
||||
if self.args.host:
|
||||
data_to_print = self.get_host(self.args.host)
|
||||
else:
|
||||
# Add the `['_meta']['hostvars']` information.
|
||||
hostvars = {}
|
||||
if len(data) > 0:
|
||||
for host in set([h for hosts in data.values() for h in hosts if h]):
|
||||
hostvars[host] = self.get_host(host, jsonify=False)
|
||||
data['_meta'] = {'hostvars': hostvars}
|
||||
|
||||
# JSONify the data.
|
||||
data_to_print = self.json_format_dict(data, pretty=True)
|
||||
print(data_to_print)
|
||||
|
||||
def get_host(self, hostname, jsonify=True):
|
||||
"""Return information about the given hostname, based on what
|
||||
the Windows Azure API provides.
|
||||
"""
|
||||
if hostname not in self.host_metadata:
|
||||
return "No host found: %s" % json.dumps(self.host_metadata)
|
||||
if jsonify:
|
||||
return json.dumps(self.host_metadata[hostname])
|
||||
return self.host_metadata[hostname]
|
||||
|
||||
def get_images(self):
|
||||
images = []
|
||||
for image in self.sms.list_os_images():
|
||||
if str(image.label).lower().find(self.args.list_images.lower()) >= 0:
|
||||
images.append(vars(image))
|
||||
return json.loads(json.dumps(images, default=lambda o: o.__dict__))
|
||||
|
||||
def is_cache_valid(self):
|
||||
"""Determines if the cache file has expired, or if it is still valid."""
|
||||
if os.path.isfile(self.cache_path_cache):
|
||||
mod_time = os.path.getmtime(self.cache_path_cache)
|
||||
current_time = time()
|
||||
if (mod_time + self.cache_max_age) > current_time:
|
||||
if os.path.isfile(self.cache_path_index):
|
||||
return True
|
||||
return False
|
||||
|
||||
def read_settings(self):
|
||||
"""Reads the settings from the .ini file."""
|
||||
config = ConfigParser.SafeConfigParser()
|
||||
config.read(os.path.dirname(os.path.realpath(__file__)) + '/windows_azure.ini')
|
||||
|
||||
# Credentials related
|
||||
if config.has_option('azure', 'subscription_id'):
|
||||
self.subscription_id = config.get('azure', 'subscription_id')
|
||||
if config.has_option('azure', 'cert_path'):
|
||||
self.cert_path = config.get('azure', 'cert_path')
|
||||
|
||||
# Cache related
|
||||
if config.has_option('azure', 'cache_path'):
|
||||
cache_path = os.path.expandvars(os.path.expanduser(config.get('azure', 'cache_path')))
|
||||
self.cache_path_cache = os.path.join(cache_path, 'ansible-azure.cache')
|
||||
self.cache_path_index = os.path.join(cache_path, 'ansible-azure.index')
|
||||
if config.has_option('azure', 'cache_max_age'):
|
||||
self.cache_max_age = config.getint('azure', 'cache_max_age')
|
||||
|
||||
def read_environment(self):
|
||||
''' Reads the settings from environment variables '''
|
||||
# Credentials
|
||||
if os.getenv("AZURE_SUBSCRIPTION_ID"):
|
||||
self.subscription_id = os.getenv("AZURE_SUBSCRIPTION_ID")
|
||||
if os.getenv("AZURE_CERT_PATH"):
|
||||
self.cert_path = os.getenv("AZURE_CERT_PATH")
|
||||
|
||||
def parse_cli_args(self):
|
||||
"""Command line argument processing"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Produce an Ansible Inventory file based on Azure',
|
||||
)
|
||||
parser.add_argument('--list', action='store_true', default=True,
|
||||
help='List nodes (default: True)')
|
||||
parser.add_argument('--list-images', action='store',
|
||||
help='Get all available images.')
|
||||
parser.add_argument('--refresh-cache',
|
||||
action='store_true', default=False,
|
||||
help='Force refresh of thecache by making API requests to Azure '
|
||||
'(default: False - use cache files)',
|
||||
)
|
||||
parser.add_argument('--host', action='store',
|
||||
help='Get all information about an instance.')
|
||||
self.args = parser.parse_args()
|
||||
|
||||
def do_api_calls_update_cache(self):
|
||||
"""Do API calls, and save data in cache files."""
|
||||
self.add_cloud_services()
|
||||
self.write_to_cache(self.inventory, self.cache_path_cache)
|
||||
self.write_to_cache(self.index, self.cache_path_index)
|
||||
|
||||
def add_cloud_services(self):
|
||||
"""Makes an Azure API call to get the list of cloud services."""
|
||||
try:
|
||||
for cloud_service in self.sms.list_hosted_services():
|
||||
self.add_deployments(cloud_service)
|
||||
except WindowsAzureError as e:
|
||||
print("Looks like Azure's API is down:")
|
||||
print("")
|
||||
print(e)
|
||||
sys.exit(1)
|
||||
|
||||
def add_deployments(self, cloud_service):
|
||||
"""Makes an Azure API call to get the list of virtual machines
|
||||
associated with a cloud service.
|
||||
"""
|
||||
try:
|
||||
for deployment in self.sms.get_hosted_service_properties(cloud_service.service_name,embed_detail=True).deployments.deployments:
|
||||
self.add_deployment(cloud_service, deployment)
|
||||
except WindowsAzureError as e:
|
||||
print("Looks like Azure's API is down:")
|
||||
print("")
|
||||
print(e)
|
||||
sys.exit(1)
|
||||
|
||||
def add_deployment(self, cloud_service, deployment):
|
||||
"""Adds a deployment to the inventory and index"""
|
||||
for role in deployment.role_instance_list.role_instances:
|
||||
try:
|
||||
# Default port 22 unless port found with name 'SSH'
|
||||
port = '22'
|
||||
for ie in role.instance_endpoints.instance_endpoints:
|
||||
if ie.name == 'SSH':
|
||||
port = ie.public_port
|
||||
break
|
||||
except AttributeError as e:
|
||||
pass
|
||||
finally:
|
||||
self.add_instance(role.instance_name, deployment, port, cloud_service, role.instance_status)
|
||||
|
||||
def add_instance(self, hostname, deployment, ssh_port, cloud_service, status):
|
||||
"""Adds an instance to the inventory and index"""
|
||||
|
||||
dest = urlparse(deployment.url).hostname
|
||||
|
||||
# Add to index
|
||||
self.index[hostname] = deployment.name
|
||||
|
||||
self.host_metadata[hostname] = dict(ansible_ssh_host=dest,
|
||||
ansible_ssh_port=int(ssh_port),
|
||||
instance_status=status,
|
||||
private_id=deployment.private_id)
|
||||
|
||||
# List of all azure deployments
|
||||
self.push(self.inventory, "azure", hostname)
|
||||
|
||||
# Inventory: Group by service name
|
||||
self.push(self.inventory, self.to_safe(cloud_service.service_name), hostname)
|
||||
|
||||
if int(ssh_port) == 22:
|
||||
self.push(self.inventory, "Cloud_services", hostname)
|
||||
|
||||
# Inventory: Group by region
|
||||
self.push(self.inventory, self.to_safe(cloud_service.hosted_service_properties.location), hostname)
|
||||
|
||||
def push(self, my_dict, key, element):
|
||||
"""Pushed an element onto an array that may not have been defined in the dict."""
|
||||
if key in my_dict:
|
||||
my_dict[key].append(element);
|
||||
else:
|
||||
my_dict[key] = [element]
|
||||
|
||||
def get_inventory_from_cache(self):
|
||||
"""Reads the inventory from the cache file and returns it as a JSON object."""
|
||||
cache = open(self.cache_path_cache, 'r')
|
||||
json_inventory = cache.read()
|
||||
return json_inventory
|
||||
|
||||
def load_index_from_cache(self):
|
||||
"""Reads the index from the cache file and sets self.index."""
|
||||
cache = open(self.cache_path_index, 'r')
|
||||
json_index = cache.read()
|
||||
self.index = json.loads(json_index)
|
||||
|
||||
def write_to_cache(self, data, filename):
|
||||
"""Writes data in JSON format to a file."""
|
||||
json_data = self.json_format_dict(data, True)
|
||||
cache = open(filename, 'w')
|
||||
cache.write(json_data)
|
||||
cache.close()
|
||||
|
||||
def to_safe(self, word):
|
||||
"""Escapes any characters that would be invalid in an ansible group name."""
|
||||
return re.sub("[^A-Za-z0-9\-]", "_", word)
|
||||
|
||||
def json_format_dict(self, data, pretty=False):
|
||||
"""Converts a dict to a JSON object and dumps it as a formatted string."""
|
||||
if pretty:
|
||||
return json.dumps(data, sort_keys=True, indent=2)
|
||||
else:
|
||||
return json.dumps(data)
|
||||
|
||||
|
||||
AzureInventory()
|
||||
@ -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:
|
||||
print("Need an argument, either --list or --host <host>")
|
||||
57
docs/man/man1/ansible-doc.1
Normal file
57
docs/man/man1/ansible-doc.1
Normal file
@ -0,0 +1,57 @@
|
||||
'\" t
|
||||
.\" Title: ansible-doc
|
||||
.\" Author: [see the "AUTHOR" section]
|
||||
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
|
||||
.\" Date: 11/27/2013
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 1.4.1
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE\-DOC" "1" "11/27/2013" "Ansible 1\&.4\&.1" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * set default formatting
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" disable hyphenation
|
||||
.nh
|
||||
.\" disable justification (adjust text to left margin only)
|
||||
.ad l
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * MAIN CONTENT STARTS HERE *
|
||||
.\" -----------------------------------------------------------------
|
||||
.SH "NAME"
|
||||
ansible-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 as well as IRC and mailing list info is available on the ansible home page: https://ansible\&.github\&.com/
|
||||
@ -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.78.1 <http://docbook.sf.net/>
|
||||
.\" Date: 03/16/2014
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 1.6
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE\-GALAXY" "1" "03/16/2014" "Ansible 1\&.6" "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,10 +122,6 @@ 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
|
||||
----
|
||||
|
||||
@ -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. Provids the ID of each integration
|
||||
which can be used with the remove option.
|
||||
|
||||
--remove::
|
||||
|
||||
Remove a specific integration. Provide the ID of the integration to
|
||||
be removed.
|
||||
|
||||
AUTHOR
|
||||
------
|
||||
|
||||
@ -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
|
||||
|
||||
251
docs/man/man1/ansible-playbook.1
Normal file
251
docs/man/man1/ansible-playbook.1
Normal file
@ -0,0 +1,251 @@
|
||||
'\" t
|
||||
.\" Title: ansible-playbook
|
||||
.\" Author: :doctype:manpage
|
||||
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
|
||||
.\" Date: 02/12/2014
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 1.5
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE\-PLAYBOOK" "1" "02/12/2014" "Ansible 1\&.5" "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\-S\fR, \fB\-\-su\fR
|
||||
.RS 4
|
||||
run operations with su\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-ask\-su\-pass\fR
|
||||
.RS 4
|
||||
Prompt for the password to use for playbook plays that request su access, if any\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-R\fR, \fISU_USER\fR, \fB\-\-sudo\-user=\fR\fISU_USER\fR
|
||||
.RS 4
|
||||
Desired su user (default=root)\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-ask\-vault\-pass\fR
|
||||
.RS 4
|
||||
Ask for vault password\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-vault\-password\-file=\fR\fIVAULT_PASSWORD_FILE\fR
|
||||
.RS 4
|
||||
Vault password file\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-force\-handlers\fR
|
||||
.RS 4
|
||||
Run play handlers even if a task fails\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-list\-hosts\fR
|
||||
.RS 4
|
||||
Outputs a list of matching hosts without executing anything else\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-list\-tasks\fR
|
||||
.RS 4
|
||||
List all tasks that would be executed\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-start\-at\-task=\fR\fISTART_AT\fR
|
||||
.RS 4
|
||||
Start the playbook at the task matching this name\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-step\fR
|
||||
.RS 4
|
||||
one-step-at-a-time: confirm each task before running\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-syntax\-check\fR
|
||||
.RS 4
|
||||
Perform a syntax check on the playbook, but do not execute it\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-private\-key\fR
|
||||
.RS 4
|
||||
Use this file to authenticate the connection\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-t\fR, \fITAGS\fR, \fB\fI\-\-tags=\fR\fR\fB\*(AqTAGS\fR
|
||||
.RS 4
|
||||
Only run plays and tasks tagged with these values\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\fI\-\-skip\-tags=\fR\fR\fB\*(AqSKIP_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
|
||||
|
||||
.PP
|
||||
\fB\-\-version\fR
|
||||
.RS 4
|
||||
Show program's version number and exit\&.
|
||||
.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
|
||||
@ -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 in 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,55 +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).
|
||||
|
||||
*--skip-tages=*'SKIP_TAGS'::
|
||||
*-f* 'NUM', *--forks=*'NUM'::
|
||||
|
||||
Only run plays and tasks whose tags do not match these values.
|
||||
Level of parallelism. 'NUM' is specified as an integer, the default is 5.
|
||||
|
||||
*--start-at-task=*'START_AT'::
|
||||
|
||||
Start the playbook at the task matching this name.
|
||||
*-k*, *--ask-pass*::
|
||||
|
||||
*--step*::
|
||||
Prompt for the SSH password instead of assuming key-based
|
||||
authentication with ssh-agent.
|
||||
|
||||
One-step-at-a-time: confirm each task before running.
|
||||
|
||||
*-S*, --su*::
|
||||
*-K*, *--ask-sudo-pass*::
|
||||
|
||||
Run operations with su (deprecated, use become)
|
||||
Prompt for the password to use for playbook plays that request sudo
|
||||
access, if any.
|
||||
|
||||
*-R SU-USER*, *--su-user=*'SU_USER'::
|
||||
*-U*, 'SUDO_USER', *--sudo-user=*'SUDO_USER'::
|
||||
|
||||
run operations with su as this user (default=root) (deprecated, use become)
|
||||
Desired sudo user (default=root).
|
||||
|
||||
*-s*, *--sudo*::
|
||||
*-t*, 'TAGS', *--tags=*'TAGS'::
|
||||
|
||||
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' deafult is root. (deprecated, use become).
|
||||
Only run plays and tasks tagged with these values.
|
||||
|
||||
*--skip-tags=*'SKIP_TAGS'::
|
||||
|
||||
@ -184,30 +88,40 @@ Only run plays and tasks whose tags do not match these values.
|
||||
|
||||
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.
|
||||
|
||||
*--version*::
|
||||
|
||||
Show program's version number and exit.
|
||||
Further limits the selected host/group patterns.
|
||||
|
||||
|
||||
ENVIRONMENT
|
||||
@ -215,15 +129,10 @@ ENVIRONMENT
|
||||
|
||||
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
|
||||
-----
|
||||
|
||||
@ -235,7 +144,6 @@ FILES
|
||||
|
||||
~/.ansible.cfg -- User config file, overrides the default config if present
|
||||
|
||||
|
||||
AUTHOR
|
||||
------
|
||||
|
||||
@ -254,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
|
||||
|
||||
103
docs/man/man1/ansible-pull.1
Normal file
103
docs/man/man1/ansible-pull.1
Normal file
@ -0,0 +1,103 @@
|
||||
'\" t
|
||||
.\" Title: ansible
|
||||
.\" Author: :doctype:manpage
|
||||
.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
|
||||
.\" Date: 01/02/2014
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 1.5
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE" "1" "01/03/2014" "Ansible 1\&.5" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * set default formatting
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" disable hyphenation
|
||||
.nh
|
||||
.\" disable justification (adjust text to left margin only)
|
||||
.ad l
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * MAIN CONTENT STARTS HERE *
|
||||
.\" -----------------------------------------------------------------
|
||||
.SH "NAME"
|
||||
ansible-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 as well as IRC and mailing list info is available on the ansible home page: https://ansible\&.github\&.com/
|
||||
.SH "AUTHOR"
|
||||
.PP
|
||||
\fB:doctype:manpage\fR
|
||||
.RS 4
|
||||
Author.
|
||||
.RE
|
||||
@ -5,21 +5,21 @@ ansible(1)
|
||||
: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,107 +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.
|
||||
|
||||
*-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.78.1 <http://docbook.sf.net/>
|
||||
.\" Date: 03/17/2014
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 1.6
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE\-VAULT" "1" "03/17/2014" "Ansible 1\&.6" "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 '--valut-password-file' is not supplied ansib-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 vim. 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
|
||||
|
||||
199
docs/man/man1/ansible.1
Normal file
199
docs/man/man1/ansible.1
Normal file
@ -0,0 +1,199 @@
|
||||
'\" t
|
||||
.\" Title: ansible
|
||||
.\" Author: [see the "AUTHOR" section]
|
||||
.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
|
||||
.\" Date: 11/27/2013
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 1.4.1
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE" "1" "11/27/2013" "Ansible 1\&.4\&.1" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * set default formatting
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" disable hyphenation
|
||||
.nh
|
||||
.\" disable justification (adjust text to left margin only)
|
||||
.ad l
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * MAIN CONTENT STARTS HERE *
|
||||
.\" -----------------------------------------------------------------
|
||||
.SH "NAME"
|
||||
ansible \- run a command somewhere else
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
ansible <host\-pattern> [\-f forks] [\-m module_name] [\-a args]
|
||||
.SH "DESCRIPTION"
|
||||
.sp
|
||||
\fBAnsible\fR is an extra\-simple tool/framework/API for doing \'remote things\' over SSH\&.
|
||||
.SH "ARGUMENTS"
|
||||
.PP
|
||||
\fBhost\-pattern\fR
|
||||
.RS 4
|
||||
A name of a group in the inventory file, a shell\-like glob selecting hosts in inventory file, or any combination of the two separated by semicolons\&.
|
||||
.RE
|
||||
.SH "OPTIONS"
|
||||
.PP
|
||||
\fB\-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 \'\fIARGUMENTS\fR\', \fB\-\-args=\fR\'\fIARGUMENTS\fR\'
|
||||
.RS 4
|
||||
The
|
||||
\fIARGUMENTS\fR
|
||||
to pass to the module\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-k\fR, \fB\-\-ask\-pass\fR
|
||||
.RS 4
|
||||
Prompt for the SSH password instead of assuming key\-based authentication with ssh\-agent\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-K\fR, \fB\-\-ask\-sudo\-pass\fR
|
||||
.RS 4
|
||||
Prompt for the password to use with \-\-sudo, if any
|
||||
.RE
|
||||
.PP
|
||||
\fB\-o\fR, \fB\-\-one\-line\fR
|
||||
.RS 4
|
||||
Try to output everything on one line\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-s\fR, \fB\-\-sudo\fR
|
||||
.RS 4
|
||||
Run the command as the user given by \-u and sudo to root\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-t\fR \fIDIRECTORY\fR, \fB\-\-tree=\fR\fIDIRECTORY\fR
|
||||
.RS 4
|
||||
Save contents in this output
|
||||
\fIDIRECTORY\fR, with the results saved in a file named after each host\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-T\fR \fISECONDS\fR, \fB\-\-timeout=\fR\fISECONDS\fR
|
||||
.RS 4
|
||||
Connection timeout to use when trying to talk to hosts, in
|
||||
\fISECONDS\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-B\fR \fINUM\fR, \fB\-\-background=\fR\fINUM\fR
|
||||
.RS 4
|
||||
Run commands in the background, killing the task after
|
||||
\fINUM\fR
|
||||
seconds\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-P\fR \fINUM\fR, \fB\-\-poll=\fR\fINUM\fR
|
||||
.RS 4
|
||||
Poll a background job every
|
||||
\fINUM\fR
|
||||
seconds\&. Requires
|
||||
\fB\-B\fR\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-u\fR \fIUSERNAME\fR, \fB\-\-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://ansible\&.github\&.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 as well as IRC and mailing list info is available on the ansible home page: https://ansible\&.github\&.com/
|
||||
@ -7,19 +7,19 @@ ansible(1)
|
||||
|
||||
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
|
||||
@ -27,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 | runas | doas ]
|
||||
|
||||
*--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 traling 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'::
|
||||
|
||||
@ -180,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
|
||||
-----
|
||||
|
||||
@ -236,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
|
||||
|
||||
@ -10,18 +10,12 @@ docs: clean modules staticmin
|
||||
-(cp *.jpg htmlout/)
|
||||
-(cp *.png htmlout/)
|
||||
|
||||
variables:
|
||||
(mkdir -p htmlout/)
|
||||
dot variables.dot -Tpng -o htmlout/variables.png
|
||||
|
||||
viewdocs: clean staticmin
|
||||
./build-site.py view
|
||||
|
||||
htmldocs: staticmin
|
||||
./build-site.py rst
|
||||
|
||||
webdocs: htmldocs
|
||||
|
||||
clean:
|
||||
-rm -rf htmlout
|
||||
-rm -f .buildinfo
|
||||
@ -42,7 +36,7 @@ clean:
|
||||
.PHONEY: docs clean
|
||||
|
||||
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/^[ \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();
|
||||
});
|
||||
});
|
||||
@ -13,13 +13,13 @@
|
||||
<hr/>
|
||||
|
||||
<p>
|
||||
© Copyright 2015 <a href="http://ansible.com">Ansible, Inc.</a>.
|
||||
© 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>
|
||||
|
||||
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 %}
|
||||
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>
|
||||
|
||||
@ -14,11 +14,18 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<!-- Google Tag Manager Data Layer -->
|
||||
<script>
|
||||
dataLayer = [];
|
||||
</script>
|
||||
<!-- End Google Tag Manager Data Layer -->
|
||||
<!-- begin analytics -->
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', 'UA-29861888-3', 'ansible.com');
|
||||
ga('send', 'pageview');
|
||||
|
||||
</script>
|
||||
<!-- end analytics -->
|
||||
|
||||
{% block htmltitle %}
|
||||
<title>{{ title|striptags|e }}{{ titlesuffix }}</title>
|
||||
@ -113,44 +120,20 @@
|
||||
}
|
||||
</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">
|
||||
|
||||
<!-- Google Tag Manager -->
|
||||
<noscript><iframe src="//www.googletagmanager.com/ns.html?id=GTM-PSB293" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
|
||||
<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:#5bbdbf;height=80px;margin:'auto auto auto auto'">
|
||||
<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>
|
||||
@ -195,17 +178,6 @@
|
||||
{# PAGE CONTENT #}
|
||||
<div class="wy-nav-content">
|
||||
<div class="rst-content">
|
||||
|
||||
<!-- Tower ads -->
|
||||
<a class="DocSiteBanner" href="http://www.ansible.com/tower?utm_source=docs">
|
||||
<div class="DocSiteBanner-imgWrapper">
|
||||
<img src="{{ pathto('_static/', 1) }}images/banner_ad_1.png">
|
||||
</div>
|
||||
<div class="DocSiteBanner-imgWrapper">
|
||||
<img src="{{ pathto('_static/', 1) }}images/banner_ad_2.png">
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{% include "breadcrumbs.html" %}
|
||||
<div id="page-content">
|
||||
{% block body %}{% endblock %}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
var cx = '006019874985968165468:eu5pbnxp4po';
|
||||
var cx = '015514069825911246756:eu5pbnxp4po';
|
||||
var gcse = document.createElement('script');
|
||||
gcse.type = 'text/javascript';
|
||||
gcse.async = true;
|
||||
|
||||
@ -3293,7 +3293,7 @@ code, .rst-content tt {
|
||||
border: solid 1px #e1e4e5;
|
||||
font-size: 75%;
|
||||
padding: 0 5px;
|
||||
font-family: "Inconsolata", "Consolata", "Monaco", monospace;
|
||||
font-family: "Incosolata", "Consolata", "Monaco", monospace;
|
||||
color: #e74c3c;
|
||||
overflow-x: auto;
|
||||
}
|
||||
@ -3439,7 +3439,7 @@ a.wy-text-neutral:hover {
|
||||
border-right: solid 1px #e6e9ea;
|
||||
margin: 0;
|
||||
padding: 12px 12px;
|
||||
font-family: "Inconsolata", "Consolata", "Monaco", monospace;
|
||||
font-family: "Incosolata", "Consolata", "Monaco", monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: #d9d9d9;
|
||||
@ -3449,7 +3449,7 @@ div[class^='highlight'] pre {
|
||||
white-space: pre;
|
||||
margin: 0;
|
||||
padding: 12px 12px;
|
||||
font-family: "Inconsolata", "Consolata", "Monaco", monospace;
|
||||
font-family: "Incosolata", "Consolata", "Monaco", monospace;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
display: block;
|
||||
@ -4634,122 +4634,3 @@ span[id*='MathJax-Span'] {
|
||||
#search-box-id {
|
||||
padding-right: 25px;
|
||||
}
|
||||
|
||||
.DocSiteProduct-header {
|
||||
flex: 1;
|
||||
-webkit-flex: 1;
|
||||
padding: 20px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 20px;
|
||||
display: flex;
|
||||
display: -webkit-flex;
|
||||
flex-direction: column;
|
||||
-webkit-flex-direction: column;
|
||||
align-items: center;
|
||||
-webkit-align-items: center;
|
||||
justify-content: flex-start;
|
||||
-webkit-justify-content: flex-start;
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
text-decoration: none;
|
||||
font-weight: 400;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
}
|
||||
|
||||
.DocSiteProduct-header:active,
|
||||
.DocSiteProduct-header:focus {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.DocSiteProduct-header:visited {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.DocSiteProduct-header--core {
|
||||
font-size: 25px;
|
||||
background-color: #5bbdbf;
|
||||
border: 2px solid #5bbdbf;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.DocSiteProduct-headerAlign {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.DocSiteProduct-logo {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
margin-bottom: -9px;
|
||||
}
|
||||
|
||||
.DocSiteProduct-logoText {
|
||||
margin-top: 6px;
|
||||
font-size: 25px;
|
||||
}
|
||||
|
||||
.DocSite-nav {
|
||||
flex: initial;
|
||||
-webkit-flex: initial;
|
||||
display: flex;
|
||||
display: -webkit-flex;
|
||||
flex-direction: row;
|
||||
-webkit-flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
-webkit-justify-content: flex-start;
|
||||
padding: 15px;
|
||||
background-color: #000;
|
||||
text-decoration: none;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
}
|
||||
|
||||
.DocSiteNav-logo {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.DocSiteNav-title {
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.wy-menu-vertical a {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.wy-menu-vertical a.reference.internal {
|
||||
padding: 0.4045em 1.618em;
|
||||
}
|
||||
|
||||
|
||||
.DocSiteBanner {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
display: -webkit-flex;
|
||||
flex-wrap: wrap;
|
||||
-webkit-flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
-webkit-justify-content: space-between;
|
||||
background-color: #ff5850;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.DocSiteBanner-imgWrapper {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1403px) {
|
||||
.DocSiteBanner {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
display: -webkit-flex;
|
||||
flex-wrap: wrap;
|
||||
-webkit-flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
-webkit-justify-content: center;
|
||||
background-color: #fff;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 8.1 KiB |
@ -15,7 +15,6 @@
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
from __future__ import print_function
|
||||
|
||||
__docformat__ = 'restructuredtext'
|
||||
|
||||
@ -25,9 +24,9 @@ import traceback
|
||||
try:
|
||||
from sphinx.application import Sphinx
|
||||
except ImportError:
|
||||
print("#################################")
|
||||
print("Dependency missing: Python Sphinx")
|
||||
print("#################################")
|
||||
print "#################################"
|
||||
print "Dependency missing: Python Sphinx"
|
||||
print "#################################"
|
||||
sys.exit(1)
|
||||
import os
|
||||
|
||||
@ -41,7 +40,7 @@ class SphinxBuilder(object):
|
||||
"""
|
||||
Run the DocCommand.
|
||||
"""
|
||||
print("Creating html documentation ...")
|
||||
print "Creating html documentation ..."
|
||||
|
||||
try:
|
||||
buildername = 'html'
|
||||
@ -70,10 +69,10 @@ class SphinxBuilder(object):
|
||||
|
||||
app.builder.build_all()
|
||||
|
||||
except ImportError:
|
||||
except ImportError, ie:
|
||||
traceback.print_exc()
|
||||
except Exception as ex:
|
||||
print("FAIL! exiting ... (%s)" % ex, file=sys.stderr)
|
||||
except Exception, ex:
|
||||
print >> sys.stderr, "FAIL! exiting ... (%s)" % ex
|
||||
|
||||
def build_docs(self):
|
||||
self.app.builder.build_all()
|
||||
@ -84,14 +83,21 @@ def build_rst_docs():
|
||||
|
||||
if __name__ == '__main__':
|
||||
if '-h' in sys.argv or '--help' in sys.argv:
|
||||
print("This script builds the html documentation from rst/asciidoc sources.\n")
|
||||
print(" Run 'make docs' to build everything.")
|
||||
print(" Run 'make viewdocs' to build and then preview in a web browser.")
|
||||
print "This script builds the html documentation from rst/asciidoc sources.\n"
|
||||
print " Run 'make docs' to build everything."
|
||||
print " Run 'make viewdocs' to build and then preview in a web browser."
|
||||
sys.exit(0)
|
||||
|
||||
build_rst_docs()
|
||||
# The 'htmldocs' make target will call this scrip twith the 'rst'
|
||||
# parameter' We don't need to run the 'htmlman' target then.
|
||||
if "rst" in sys.argv:
|
||||
build_rst_docs()
|
||||
else:
|
||||
# By default, preform the rst->html transformation and then
|
||||
# the asciidoc->html trasnformation
|
||||
build_rst_docs()
|
||||
|
||||
if "view" in sys.argv:
|
||||
import webbrowser
|
||||
if not webbrowser.open('htmlout/index.html'):
|
||||
print("Could not open on your webbrowser.", file=sys.stderr)
|
||||
print >> sys.stderr, "Could not open on your webbrowser."
|
||||
|
||||
@ -55,7 +55,7 @@ master_doc = 'index'
|
||||
|
||||
# General substitutions.
|
||||
project = 'Ansible Documentation'
|
||||
copyright = "2013-2015 Ansible, Inc"
|
||||
copyright = "2013 Ansible, Inc"
|
||||
|
||||
# The default replacements for |version| and |release|, also used in various
|
||||
# other places throughout the built documents.
|
||||
@ -100,8 +100,6 @@ exclude_patterns = ['modules']
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
highlight_language = 'YAML'
|
||||
|
||||
|
||||
# Options for HTML output
|
||||
# -----------------------
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 1.1 KiB |
@ -20,52 +20,52 @@ Each item in the list is a list of key/value pairs, commonly
|
||||
called a "hash" or a "dictionary". So, we need to know how
|
||||
to write lists and dictionaries in YAML.
|
||||
|
||||
There's another small quirk to YAML. All YAML files (regardless of their association with Ansible or not) can optionally
|
||||
begin with ``---`` and end with ``...``. This is part of the YAML format and indicates the start and end of a document.
|
||||
There's another small quirk to YAML. All YAML files (regardless of their association with
|
||||
Ansible or not) should begin with ``---``. This is part of the YAML
|
||||
format and indicates the start of a document.
|
||||
|
||||
All members of a list are lines beginning at the same indentation level starting with a ``"- "`` (a dash and a space)::
|
||||
All members of a list are lines beginning at the same indentation level starting
|
||||
with a ``-`` (dash) character::
|
||||
|
||||
---
|
||||
# A list of tasty fruits
|
||||
fruits:
|
||||
- Apple
|
||||
- Orange
|
||||
- Strawberry
|
||||
- Mango
|
||||
...
|
||||
- Apple
|
||||
- Orange
|
||||
- Strawberry
|
||||
- Mango
|
||||
|
||||
A dictionary is represented in a simple ``key: value`` form (the colon must be followed by a space)::
|
||||
|
||||
# An employee record
|
||||
- martin:
|
||||
name: Martin D'vloper
|
||||
job: Developer
|
||||
skill: Elite
|
||||
|
||||
Dictionaries and lists can also be represented in an abbreviated form if you really want to::
|
||||
A dictionary is represented in a simple ``key:`` and ``value`` form::
|
||||
|
||||
---
|
||||
employees:
|
||||
- martin: {name: Martin D'vloper, job: Developer, skill: Elite}
|
||||
fruits: ['Apple', 'Orange', 'Strawberry', 'Mango]
|
||||
# An employee record
|
||||
name: Example Developer
|
||||
job: Developer
|
||||
skill: Elite
|
||||
|
||||
Dictionaries can also be represented in an abbreviated form if you really want to::
|
||||
|
||||
---
|
||||
# An employee record
|
||||
{name: Example Developer, job: Developer, skill: Elite}
|
||||
|
||||
.. _truthiness:
|
||||
|
||||
Ansible doesn't really use these too much, but you can also specify a boolean value (true/false) in several forms::
|
||||
Ansible doesn't really use these too much, but you can also specify a
|
||||
boolean value (true/false) in several forms::
|
||||
|
||||
---
|
||||
create_key: yes
|
||||
needs_agent: no
|
||||
knows_oop: True
|
||||
likes_emacs: TRUE
|
||||
uses_cvs: false
|
||||
|
||||
|
||||
Let's combine what we learned so far in an arbitrary YAML example.
|
||||
This really has nothing to do with Ansible, but will give you a feel for the format::
|
||||
Let's combine what we learned so far in an arbitrary YAML example. This really
|
||||
has nothing to do with Ansible, but will give you a feel for the format::
|
||||
|
||||
---
|
||||
# An employee record
|
||||
name: Martin D'vloper
|
||||
name: Example Developer
|
||||
job: Developer
|
||||
skill: Elite
|
||||
employed: True
|
||||
@ -79,16 +79,17 @@ This really has nothing to do with Ansible, but will give you a feel for the for
|
||||
python: Elite
|
||||
dotnet: Lame
|
||||
|
||||
That's all you really need to know about YAML to start writing `Ansible` playbooks.
|
||||
That's all you really need to know about YAML to start writing
|
||||
`Ansible` playbooks.
|
||||
|
||||
Gotchas
|
||||
-------
|
||||
|
||||
While YAML is generally friendly, the following is going to result in a YAML syntax error::
|
||||
While YAML is generally friendly, the following is going to result in a YAML syntax error:
|
||||
|
||||
foo: somebody said I should put a colon here: so I did
|
||||
|
||||
You will want to quote any hash values using colons, like so::
|
||||
You will want to quote any hash values using colons, like so:
|
||||
|
||||
foo: "somebody said I should put a colon here: so I did"
|
||||
|
||||
@ -99,14 +100,6 @@ with a "{", YAML will think it is a dictionary, so you must quote it, like so::
|
||||
|
||||
foo: "{{ variable }}"
|
||||
|
||||
The same applies for strings that start or contain any YAML special characters `` [] {} : > | `` .
|
||||
|
||||
Boolean conversion is helpful, but this can be a problem when you want a literal `yes` or other boolean values as a string.
|
||||
In these cases just use quotes::
|
||||
|
||||
non_boolean: "yes"
|
||||
other_string: "False"
|
||||
|
||||
|
||||
.. seealso::
|
||||
|
||||
@ -114,7 +107,7 @@ In these cases just use quotes::
|
||||
Learn what playbooks can do and how to write/run them.
|
||||
`YAMLLint <http://yamllint.com/>`_
|
||||
YAML Lint (online) helps you debug YAML syntax if you are having problems
|
||||
`Github examples directory <https://github.com/ansible/ansible-examples>`_
|
||||
`Github examples directory <https://github.com/ansible/ansible/tree/devel/examples/playbooks>`_
|
||||
Complete playbook files from the github project source
|
||||
`Mailing List <http://groups.google.com/group/ansible-project>`_
|
||||
Questions? Help? Ideas? Stop by the list on Google Groups
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
.. note::
|
||||
|
||||
Ansible 2.0 has deprecated the “ssh” from ``ansible_ssh_user``, ``ansible_ssh_host``, and ``ansible_ssh_port`` to become ``ansible_user``, ``ansible_host``, and ``ansible_port``. If you are using a version of Ansible prior to 2.0, you should continue using the older style variables (``ansible_ssh_*``). These shorter variables are ignored, without warning, in older versions of Ansible.
|
||||
@ -1,147 +0,0 @@
|
||||
Become (Privilege Escalation)
|
||||
+++++++++++++++++++++++++++++
|
||||
|
||||
Ansible can use existing privilege escalation systems to allow a user to execute tasks as another.
|
||||
|
||||
.. contents:: Topics
|
||||
|
||||
Become
|
||||
``````
|
||||
Before 1.9 Ansible mostly allowed the use of `sudo` and a limited use of `su` to allow a login/remote user to become a different user
|
||||
and execute tasks, create resources with the 2nd user's permissions. As of 1.9 `become` supersedes the old sudo/su, while still
|
||||
being backwards compatible. This new system also makes it easier to add other privilege escalation tools like `pbrun` (Powerbroker),
|
||||
`pfexec` and others.
|
||||
|
||||
|
||||
New directives
|
||||
--------------
|
||||
|
||||
become
|
||||
equivalent to adding `sudo:` or `su:` to a play or task, set to 'true'/'yes' to activate privilege escalation
|
||||
|
||||
become_user
|
||||
equivalent to adding 'sudo_user:' or 'su_user:' to a play or task, set to user with desired privileges
|
||||
|
||||
become_method
|
||||
at play or task level overrides the default method set in ansible.cfg, set to 'sudo'/'su'/'pbrun'/'pfexec'/'doas'
|
||||
|
||||
|
||||
New ansible\_ variables
|
||||
-----------------------
|
||||
Each allows you to set an option per group and/or host
|
||||
|
||||
ansible_become
|
||||
equivalent to ansible_sudo or ansible_su, allows to force privilege escalation
|
||||
|
||||
ansible_become_method
|
||||
allows to set privilege escalation method
|
||||
|
||||
ansible_become_user
|
||||
equivalent to ansible_sudo_user or ansible_su_user, allows to set the user you become through privilege escalation
|
||||
|
||||
ansible_become_pass
|
||||
equivalent to ansible_sudo_pass or ansible_su_pass, allows you to set the privilege escalation password
|
||||
|
||||
|
||||
New command line options
|
||||
------------------------
|
||||
|
||||
--ask-become-pass
|
||||
ask for privilege escalation password
|
||||
|
||||
--become,-b
|
||||
run operations with become (no password implied)
|
||||
|
||||
--become-method=BECOME_METHOD
|
||||
privilege escalation method to use (default=sudo),
|
||||
valid choices: [ sudo | su | pbrun | pfexec | doas ]
|
||||
|
||||
--become-user=BECOME_USER
|
||||
run operations as this user (default=root)
|
||||
|
||||
|
||||
sudo and su still work!
|
||||
-----------------------
|
||||
|
||||
Old playbooks will not need to be changed, even though they are deprecated, sudo and su directives will continue to work though it
|
||||
is recommended to move to become as they may be retired at one point. You cannot mix directives on the same object though, Ansible
|
||||
will complain if you try to.
|
||||
|
||||
Become will default to using the old sudo/su configs and variables if they exist, but will override them if you specify any of the
|
||||
new ones.
|
||||
|
||||
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
Although privilege escalation is mostly intuitive, there are a few limitations
|
||||
on how it works. Users should be aware of these to avoid surprises.
|
||||
|
||||
Becoming an Unprivileged User
|
||||
=============================
|
||||
|
||||
Ansible has a limitation with regards to becoming an
|
||||
unprivileged user that can be a security risk if users are not aware of it.
|
||||
Ansible modules are executed on the remote machine by first substituting the
|
||||
parameters into the module file, then copying the file to the remote machine,
|
||||
and finally executing it there. If the module file is executed without using
|
||||
become, when the become user is root, or when the connection to the remote
|
||||
machine is made as root then the module file is created with permissions that
|
||||
only allow reading by the user and root.
|
||||
|
||||
If the become user is an unprivileged user and then Ansible has no choice but
|
||||
to make the module file world readable as there's no other way for the user
|
||||
Ansible connects as to save the file so that the user that we're becoming can
|
||||
read it.
|
||||
|
||||
If any of the parameters passed to the module are sensitive in nature then
|
||||
those pieces of data are readable by reading the module file for the duration
|
||||
of the Ansible module execution. Once the module is done executing Ansible
|
||||
will delete the temporary file. If you trust the client machines then there's
|
||||
no problem here. If you do not trust the client machines then this is
|
||||
a potential danger.
|
||||
|
||||
Ways to resolve this include:
|
||||
|
||||
* Use :ref:`pipelining`. When pipelining is enabled, Ansible doesn't save the
|
||||
module to a temporary file on the client. Instead it pipes the module to
|
||||
the remote python interpreter's stdin. Pipelining does not work for
|
||||
non-python modules.
|
||||
|
||||
* Don't perform an action on the remote machine by becoming an unprivileged
|
||||
user. Temporary files are protected by UNIX file permissions when you
|
||||
become root or do not use become.
|
||||
|
||||
Connection Plugin Support
|
||||
=========================
|
||||
|
||||
Privilege escalation methods must also be supported by the connection plugin
|
||||
used. Most connection plugins will warn if they do not support become. Some
|
||||
will just ignore it as they always run as root (jail, chroot, etc).
|
||||
|
||||
Only one method may be enabled per host
|
||||
=======================================
|
||||
|
||||
Methods cannot be chained. You cannot use ``sudo /bin/su -`` to become a user,
|
||||
you need to have privileges to run the command as that user in sudo or be able
|
||||
to su directly to it (the same for pbrun, pfexec or other supported methods).
|
||||
|
||||
Can't limit escalation to certain commands
|
||||
==========================================
|
||||
|
||||
Privilege escalation permissions have to be general. Ansible does not always
|
||||
use a specific command to do something but runs modules (code) from
|
||||
a temporary file name which changes every time. If you have '/sbin/service'
|
||||
or '/bin/chmod' as the allowed commands this will fail with ansible as those
|
||||
paths won't match with the temporary file that ansible creates to run the
|
||||
module.
|
||||
|
||||
|
||||
.. seealso::
|
||||
|
||||
`Mailing List <http://groups.google.com/group/ansible-project>`_
|
||||
Questions? Help? Ideas? Stop by the list on Google Groups
|
||||
`irc.freenode.net <http://irc.freenode.net>`_
|
||||
#ansible IRC chat channel
|
||||
|
||||
@ -1,50 +0,0 @@
|
||||
Common Return Values
|
||||
====================
|
||||
|
||||
.. contents:: Topics
|
||||
|
||||
Ansible modules normally return a data structure that can be registered into a variable, or seen directly when using
|
||||
the `ansible` program as output. Here we document the values common to all modules, each module can optionally document
|
||||
its own unique returns. If these docs exist they will be visible through ansible-doc and https://docs.ansible.com.
|
||||
|
||||
.. _facts:
|
||||
|
||||
Facts
|
||||
`````
|
||||
|
||||
Some modules return 'facts' to ansible (i.e setup), this is done through a 'ansible_facts' key and anything inside
|
||||
will automatically be available for the current host directly as a variable and there is no need to
|
||||
register this data.
|
||||
|
||||
|
||||
.. _status:
|
||||
|
||||
Status
|
||||
``````
|
||||
|
||||
Every module must return a status, saying if the module was successful, if anything changed or not. Ansible itself
|
||||
will return a status if it skips the module due to a user condition (when: ) or running in check mode when the module
|
||||
does not support it.
|
||||
|
||||
|
||||
.. _other:
|
||||
|
||||
Other common returns
|
||||
````````````````````
|
||||
|
||||
It is common on failure or success to return a 'msg' that either explains the failure or makes a note about the execution.
|
||||
Some modules, specifically those that execute shell or commands directly, will return stdout and stderr, if ansible sees
|
||||
a stdout in the results it will append a stdout_lines which is just a list or the lines in stdout.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`modules`
|
||||
Learn about available modules
|
||||
`GitHub Core modules directory <https://github.com/ansible/ansible-modules-core/tree/devel>`_
|
||||
Browse source of core modules
|
||||
`Github Extras modules directory <https://github.com/ansible/ansible-modules-extras/tree/devel>`_
|
||||
Browse source of extras modules.
|
||||
`Mailing List <http://groups.google.com/group/ansible-devel>`_
|
||||
Development mailing list
|
||||
`irc.freenode.net <http://irc.freenode.net>`_
|
||||
#ansible IRC chat channel
|
||||
@ -1,305 +1,9 @@
|
||||
Community Information & Contributing
|
||||
````````````````````````````````````
|
||||
Community Information
|
||||
`````````````````````
|
||||
|
||||
Ansible is an open source project designed to bring together administrators and developers of all kinds to collaborate on building
|
||||
IT automation solutions that work well for them.
|
||||
Ansible is an open source project designed to bring together developers and administrators of all kinds to collaborate on building
|
||||
IT automation solutions that work well for them. Should you wish to get more involved -- whether in terms of just asking a question, helping other users, introducing new people to Ansible, or helping with the software or documentation, we welcome your contributions to the project.
|
||||
|
||||
Should you wish to get more involved -- whether in terms of just asking a question, helping other users, introducing new people to Ansible, or helping with the software or documentation, we welcome your contributions to the project.
|
||||
`Ways to interact <https://github.com/ansible/ansible/blob/devel/CONTRIBUTING.md>`_
|
||||
|
||||
.. contents:: Topics
|
||||
|
||||
Ansible Users
|
||||
=============
|
||||
|
||||
I've Got A Question
|
||||
-------------------
|
||||
|
||||
We're happy to help!
|
||||
|
||||
Ansible questions are best asked on the `Ansible Google Group Mailing List <http://groups.google.com/group/ansible-project>`_.
|
||||
|
||||
This is a very large high-traffic list for answering questions and sharing tips
|
||||
and tricks. Anyone can join, and email delivery is optional if you just want to read the group online. To cut down on spam, your first post is moderated, though posts are approved quickly.
|
||||
|
||||
Please be sure to share any relevant commands you ran, output, and detail, indicate the version of Ansible you are using when asking a question.
|
||||
|
||||
Where needed, link to gists or github repos to show examples, rather than sending attachments to the list.
|
||||
|
||||
We recommend using Google search to see if a topic has been answered recently, but comments found in older threads may no longer apply, depending on the topic.
|
||||
|
||||
Before you post, be sure you are running the latest stable version of Ansible. You can check this by comparing the output of 'ansible --version' with the version indicated on `PyPi <https://pypi.python.org/pypi/ansible>`.
|
||||
|
||||
Alternatively, you can also join our IRC channel - #ansible on irc.freenode.net. It's a very high traffic channel as well, if you don't get an answer you like, please stop by our mailing list, which is more likely
|
||||
to get attention of core developers since it's asynchronous.
|
||||
|
||||
I'd Like To Keep Up With Release Announcements
|
||||
----------------------------------------------
|
||||
|
||||
Release announcements are posted to ansible-project, though if you don't want to keep up with the very active list, you can join the `Ansible Announce Mailing List <http://groups.google.com/group/ansible-announce>`_
|
||||
|
||||
This is a low-traffic read-only list, where we'll share release announcements and occasionally links to major Ansible Events around the world.
|
||||
|
||||
I'd Like To Help Share and Promote Ansible
|
||||
------------------------------------------
|
||||
|
||||
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).
|
||||
|
||||
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 us <https://twitter.com/ansible>`_.
|
||||
|
||||
I'd Like To Help Ansible Move Faster
|
||||
------------------------------------
|
||||
|
||||
If you're a developer, one of the most valuable things you can do is look at the github issues list and help fix bugs. We almost always prioritize bug fixing over
|
||||
feature development, so clearing bugs out of the way is one of the best things you can do.
|
||||
|
||||
If you're not a developer, helping test pull requests for bug fixes and features is still immensely valuable. You can do this by checking out ansible, making a test
|
||||
branch off the main one, merging a GitHub issue, testing, and then commenting on that particular issue on GitHub.
|
||||
|
||||
I'd Like To Report A Bug
|
||||
------------------------------------
|
||||
|
||||
Ansible practices responsible disclosure - if this is a security related bug, email `security@ansible.com <mailto:security@ansible.com>`_ instead of filing a ticket or posting to the Google Group and you will receive a prompt response.
|
||||
|
||||
Bugs related to the core language should be reported to `github.com/ansible/ansible <https://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.
|
||||
|
||||
MODULE related bugs however should go to `ansible-modules-core <https://github.com/ansible/ansible-modules-core>`_ or `ansible-modules-extras <https://github.com/ansible/ansible-modules-extras>`_ based on the classification of the module. This is listed on the bottom of the docs page for any module.
|
||||
|
||||
When filing a bug, please use the `issue template <https://github.com/ansible/ansible/raw/devel/ISSUE_TEMPLATE.md>`_ to provide all relevant information, regardless of what repo you are filing a ticket against.
|
||||
|
||||
Knowing your ansible version and the exact commands you are running, and what you expect, saves time and helps us help everyone with their issues
|
||||
more quickly.
|
||||
|
||||
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.
|
||||
|
||||
When sharing YAML in playbooks, formatting can be preserved by using `code blocks <https://help.github.com/articles/github-flavored-markdown#fenced-code-blocks>`_.
|
||||
|
||||
For multiple-file content, we encourage use of gist.github.com. Online pastebin content can expire, so it's nice to have things around for a longer term if they
|
||||
are referenced in a ticket.
|
||||
|
||||
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 our information about priority flags below.
|
||||
|
||||
I'd Like To Help 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 for most pages, and there is an "Edit on GitHub"
|
||||
link up on those.
|
||||
|
||||
Module documentation is generated from a DOCUMENTATION structure embedded in the source code of each module, which is in either the ansible-modules-core or ansible-modules-extra repos on github, depending on the module. Information about this is always listed on the bottom of the web documentation for each module.
|
||||
|
||||
Aside from modules, the main 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>`_.
|
||||
|
||||
For Current and Prospective Developers
|
||||
=======================================
|
||||
|
||||
I'd Like To Learn How To Develop on Ansible
|
||||
-------------------------------------------
|
||||
|
||||
If you're new to Ansible and would like to figure out how to work on things, stop by the ansible-devel mailing list
|
||||
and say hi, and we can hook you up.
|
||||
|
||||
A great way to get started would be reading over some of the development documentation on the module site, and then
|
||||
finding a bug to fix or small feature to add.
|
||||
|
||||
Modules are some of the easiest places to get started.
|
||||
|
||||
Contributing Code (Features or Bugfixes)
|
||||
----------------------------------------
|
||||
|
||||
The Ansible project keeps its source on github at `github.com/ansible/ansible <https://github.com/ansible/ansible>`_ for
|
||||
the core application, and two sub repos `github.com/ansible/ansible-modules-core <https://github.com/ansible/ansible-modules-core>`_
|
||||
and `ansible/ansible-modules-extras <https://github.com/ansible/ansible-modules-extras>`_ for module related items.
|
||||
If you need to know if a module is in 'core' or 'extras', consult the web documentation page for that module.
|
||||
|
||||
The project takes contributions through `github pull requests <https://help.github.com/articles/using-pull-requests>`_.
|
||||
|
||||
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, but can be nice for large changes).
|
||||
|
||||
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, these are the same basic
|
||||
tests that will automatically run on Travis when creating the PR. There are more in depth tests in the tests/integration
|
||||
directory, classified as destructive and non_destructive, run these if they pertain to your modification. They are setup
|
||||
with tags so you can run subsets, some of the tests require cloud credentials and will only run if they are provided.
|
||||
When adding new features of fixing bugs it would be nice to add new tests to avoid regressions.
|
||||
|
||||
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.
|
||||
|
||||
If you make a mistake you do not need to close your PR, create a clean branch locally and then push to github
|
||||
with --force to overwrite the existing branch (permissible in this case as no one else should be using that
|
||||
branch as reference). Code comments won't be lost, they just won't be attached to the existing branch.
|
||||
|
||||
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.
|
||||
Be patient, your request might not get merged right away, we also try to keep the devel branch more
|
||||
or less usable so we like to examine Pull requests carefully, which takes time.
|
||||
|
||||
Patches should always be made against the 'devel' branch.
|
||||
|
||||
Keep in mind that small and focused requests are easier to examine and accept, having example cases
|
||||
also help us understand the utility of a bug fix or a new feature.
|
||||
|
||||
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 development documentation <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, we do not enforce 80 column lines, we are fine with 120-140. We do not take 'style only'
|
||||
requests unless the code is nearly unreadable, we are "PEP8ish", but not strictly compliant.
|
||||
|
||||
You can also contribute by testing and revising other requests, specially if it is one you are interested
|
||||
in using. Please keep your comments clear and to the point, courteous and constructive, tickets are not a
|
||||
good place to start discussions (ansible-devel and IRC exist for this).
|
||||
|
||||
Tip: To easily run from a checkout, source "./hacking/env-setup" and that's it -- no install
|
||||
required. You're now live!
|
||||
|
||||
Other Topics
|
||||
============
|
||||
|
||||
Ansible 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. We also offer an
|
||||
enterprise web front end to Ansible (see Tower below).
|
||||
|
||||
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 jimi_c, abadger1999, Tybstar, bcoca, and others. On the mailing list,
|
||||
we post with an @ansible.com address.
|
||||
|
||||
Mailing List Information
|
||||
------------------------
|
||||
|
||||
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 Project List <https://groups.google.com/forum/#!forum/ansible-project>`_ is for sharing Ansible Tips,
|
||||
answering questions, and general user discussion.
|
||||
|
||||
`Ansible Development List <https://groups.google.com/forum/#!forum/ansible-devel>`_ is for learning how to develop on Ansible,
|
||||
asking about prospective feature design, or discussions about extending ansible or features in progress.
|
||||
|
||||
`Ansible Announce list <https://groups.google.com/forum/#!forum/ansible-announce>`_ is a read-only list that shares information
|
||||
about new releases of Ansible, and also rare infrequent event information, such as announcements about an AnsibleFest coming up,
|
||||
which is our official conference series.
|
||||
|
||||
`Ansible Lockdown List <https://groups.google.com/forum/#!forum/ansible-lockdown>`_ is for all things related to Ansible Lockdown projects, including DISA STIG automation and CIS Benchmarks.
|
||||
|
||||
To subscribe to a group from a non-google account, you can send an email to the subscription address requesting the subscription. For example: ansible-devel+subscribe@googlegroups.com
|
||||
|
||||
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 bugfixes (reserving these for larger items),
|
||||
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.
|
||||
|
||||
Releases are also given code names based on Van Halen songs, that no one really uses.
|
||||
|
||||
Tower Support Questions
|
||||
-----------------------
|
||||
|
||||
Ansible `Tower <http://ansible.com/tower>`_ is a UI, Server, and REST endpoint for Ansible, produced by Ansible, Inc.
|
||||
|
||||
If you have a question about tower, email `support@ansible.com <mailto:support@ansible.com>`_ rather than using the IRC
|
||||
channel or the general project mailing list.
|
||||
|
||||
IRC Channel
|
||||
-----------
|
||||
|
||||
Ansible has an IRC channel #ansible on irc.freenode.net.
|
||||
|
||||
Notes on Priority Flags
|
||||
-----------------------
|
||||
|
||||
Ansible was one of the top 5 projects with the most OSS contributors on GitHub in 2013, and has over 800 contributors
|
||||
to the project to date, not to mention a very large user community that has downloaded the application well over a million
|
||||
times.
|
||||
|
||||
As a result, we have a LOT of incoming activity to process.
|
||||
|
||||
In the interest of transparency, we're telling you how we sort incoming requests.
|
||||
|
||||
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 necessarily
|
||||
mean that we'll be exhausting all of the higher-priority queues before getting to your ticket.
|
||||
|
||||
Every bit of effort helps - if you're wishing to expedite the inclusion of a P3 feature pull request for instance, the best thing you can do
|
||||
is help close P2 bug reports.
|
||||
|
||||
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 of all kinds, profanity, allegations of Cthulhu worship, or engaging in
|
||||
controversial debates (except vi vs emacs is cool).
|
||||
|
||||
The same expectations apply to community events as they do to online interactions.
|
||||
|
||||
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.
|
||||
|
||||
@ -10,8 +10,6 @@ Learn how to build modules of your own in any language, and also how to extend A
|
||||
developing_inventory
|
||||
developing_modules
|
||||
developing_plugins
|
||||
developing_test_pr
|
||||
developing_releases
|
||||
|
||||
Developers will also likely be interested in the fully-discoverable in :doc:`tower`. It's great for embedding Ansible in all manner of applications.
|
||||
|
||||
|
||||
@ -17,67 +17,11 @@ This chapter discusses the Python API.
|
||||
|
||||
.. _python_api:
|
||||
|
||||
Python API
|
||||
----------
|
||||
|
||||
The Python API is very powerful, and is how the ansible CLI and ansible-playbook
|
||||
are implemented. In version 2.0 the core ansible got rewritten and the API was mostly rewritten.
|
||||
|
||||
.. _python_api_20:
|
||||
|
||||
Python API 2.0
|
||||
--------------
|
||||
|
||||
In 2.0 things get a bit more complicated to start, but you end up with much more discrete and readable classes::
|
||||
|
||||
|
||||
#!/usr/bin/python2
|
||||
|
||||
from collections import namedtuple
|
||||
from ansible.parsing.dataloader import DataLoader
|
||||
from ansible.vars import VariableManager
|
||||
from ansible.inventory import Inventory
|
||||
from ansible.playbook.play import Play
|
||||
from ansible.executor.task_queue_manager import TaskQueueManager
|
||||
|
||||
Options = namedtuple('Options', ['connection','module_path', 'forks', 'remote_user', 'private_key_file', 'ssh_common_args', 'ssh_extra_args', 'sftp_extra_args', 'scp_extra_args', 'become', 'become_method', 'become_user', 'verbosity', 'check'])
|
||||
# initialize needed objects
|
||||
variable_manager = VariableManager()
|
||||
loader = DataLoader()
|
||||
options = Options(connection='local', module_path='/path/to/mymodules', forks=100, remote_user=None, private_key_file=None, ssh_common_args=None, ssh_extra_args=None, sftp_extra_args=None, scp_extra_args=None, become=None, become_method=None, become_user=None, verbosity=None, check=False)
|
||||
passwords = dict(vault_pass='secret')
|
||||
|
||||
# create inventory and pass to var manager
|
||||
inventory = Inventory(loader=loader, variable_manager=variable_manager, host_list='localhost')
|
||||
variable_manager.set_inventory(inventory)
|
||||
|
||||
# create play with tasks
|
||||
play_source = dict(
|
||||
name = "Ansible Play",
|
||||
hosts = 'localhost',
|
||||
gather_facts = 'no',
|
||||
tasks = [ dict(action=dict(module='debug', args=(msg='Hello Galaxy!'))) ]
|
||||
)
|
||||
play = Play().load(play_source, variable_manager=variable_manager, loader=loader)
|
||||
|
||||
# actually run it
|
||||
tqm = None
|
||||
try:
|
||||
tqm = TaskQueueManager(
|
||||
inventory=inventory,
|
||||
variable_manager=variable_manager,
|
||||
loader=loader,
|
||||
options=options,
|
||||
passwords=passwords,
|
||||
stdout_callback='default',
|
||||
)
|
||||
result = tqm.run(play)
|
||||
finally:
|
||||
if tqm is not None:
|
||||
tqm.cleanup()
|
||||
|
||||
|
||||
.. _python_api_old:
|
||||
|
||||
Python API pre 2.0
|
||||
------------------
|
||||
are implemented.
|
||||
|
||||
It's pretty simple::
|
||||
|
||||
@ -107,7 +51,7 @@ expressed in the :doc:`modules` documentation.::
|
||||
A module can return any type of JSON data it wants, so Ansible can
|
||||
be used as a framework to rapidly build powerful applications and scripts.
|
||||
|
||||
.. _detailed_api_old_example:
|
||||
.. _detailed_api_example:
|
||||
|
||||
Detailed API Example
|
||||
````````````````````
|
||||
@ -143,9 +87,9 @@ The following script prints out the uptime information for all hosts::
|
||||
for (hostname, result) in results['dark'].items():
|
||||
print "%s >>> %s" % (hostname, result)
|
||||
|
||||
Advanced programmers may also wish to read the source to ansible itself,
|
||||
for it uses the API (with all available options) to implement the ``ansible``
|
||||
command line tools (``lib/ansible/cli/``).
|
||||
Advanced programmers may also wish to read the source to ansible itself, for
|
||||
it uses the Runner() API (with all available options) to implement the
|
||||
command line tools ``ansible`` and ``ansible-playbook``.
|
||||
|
||||
.. seealso::
|
||||
|
||||
|
||||
@ -11,17 +11,9 @@ See :doc:`modules` for a list of various ones developed in core.
|
||||
Modules can be written in any language and are found in the path specified
|
||||
by `ANSIBLE_LIBRARY` or the ``--module-path`` command line option.
|
||||
|
||||
By default, everything that ships with ansible is pulled from its source tree, but
|
||||
additional paths can be added.
|
||||
|
||||
The directory "./library", alongside your top level playbooks, is also automatically
|
||||
added as a search directory.
|
||||
|
||||
Should you develop an interesting Ansible module, consider sending a pull request to the
|
||||
`modules-extras project <https://github.com/ansible/ansible-modules-extras>`_. There's also a core
|
||||
repo for more established and widely used modules. "Extras" modules may be promoted to core periodically,
|
||||
but there's no fundamental difference in the end - both ship with ansible, all in one package, regardless
|
||||
of how you acquire ansible.
|
||||
`github project <http://github.com/ansible/ansible>`_ to see about getting your module
|
||||
included in the core project.
|
||||
|
||||
.. _module_dev_tutorial:
|
||||
|
||||
@ -48,7 +40,7 @@ modules. Keep in mind, though, that some modules in ansible's source tree are
|
||||
so look at `service` or `yum`, and don't stare too close into things like `async_wrapper` or
|
||||
you'll turn to stone. Nobody ever executes async_wrapper directly.
|
||||
|
||||
Ok, let's get going with an example. We'll use Python. For starters, save this as a file named `timetest.py`::
|
||||
Ok, let's get going with an example. We'll use Python. For starters, save this as a file named `time`::
|
||||
|
||||
#!/usr/bin/python
|
||||
|
||||
@ -67,13 +59,13 @@ Testing Modules
|
||||
|
||||
There's a useful test script in the source checkout for ansible::
|
||||
|
||||
git clone git://github.com/ansible/ansible.git --recursive
|
||||
git clone git@github.com:ansible/ansible.git
|
||||
source ansible/hacking/env-setup
|
||||
chmod +x ansible/hacking/test-module
|
||||
|
||||
Let's run the script you just wrote with that::
|
||||
|
||||
ansible/hacking/test-module -m ./timetest.py
|
||||
ansible/hacking/test-module -m ./time
|
||||
|
||||
You should see output that looks something like this::
|
||||
|
||||
@ -86,7 +78,6 @@ If you did not, you might have a typo in your module, so recheck it and try agai
|
||||
Reading Input
|
||||
`````````````
|
||||
|
||||
|
||||
Let's modify the module to allow setting the current time. We'll do this by seeing
|
||||
if a key value pair in the form `time=<string>` is passed in to the module.
|
||||
|
||||
@ -191,7 +182,7 @@ a lot shorter than this::
|
||||
|
||||
Let's test that module::
|
||||
|
||||
ansible/hacking/test-module -m ./time -a "time=\"March 14 12:23\""
|
||||
ansible/hacking/test-module -m ./time -a time=\"March 14 12:23\"
|
||||
|
||||
This should return something like::
|
||||
|
||||
@ -210,7 +201,7 @@ this, just have the module return a `ansible_facts` key, like so, along with oth
|
||||
"changed" : True,
|
||||
"rc" : 5,
|
||||
"ansible_facts" : {
|
||||
"leptons" : 5000,
|
||||
"leptons" : 5000
|
||||
"colors" : {
|
||||
"red" : "FF0000",
|
||||
"white" : "FFFFFF"
|
||||
@ -219,7 +210,7 @@ this, just have the module return a `ansible_facts` key, like so, along with oth
|
||||
}
|
||||
|
||||
These 'facts' will be available to all statements called after that module (but not before) in the playbook.
|
||||
A good idea might be to make a module called 'site_facts' and always call it at the top of each playbook, though
|
||||
A good idea might be make a module called 'site_facts' and always call it at the top of each playbook, though
|
||||
we're always open to improving the selection of core facts in Ansible as well.
|
||||
|
||||
.. _common_module_boilerplate:
|
||||
@ -231,15 +222,14 @@ As mentioned, if you are writing a module in Python, there are some very powerfu
|
||||
Modules are still transferred as one file, but an arguments file is no longer needed, so these are not
|
||||
only shorter in terms of code, they are actually FASTER in terms of execution time.
|
||||
|
||||
Rather than mention these here, the best way to learn is to read some of the `source of the modules <https://github.com/ansible/ansible-modules-core>`_ that come with Ansible.
|
||||
Rather than mention these here, the best way to learn is to read some of the `source of the modules <https://github.com/ansible/ansible/tree/devel/library>`_ that come with Ansible.
|
||||
|
||||
The 'group' and 'user' modules are reasonably non-trivial and showcase what this looks like.
|
||||
|
||||
Key parts include always ending the module file with::
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
main()
|
||||
|
||||
And instantiating the module class like::
|
||||
|
||||
@ -263,7 +253,7 @@ And failures are just as simple (where 'msg' is a required parameter to explain
|
||||
|
||||
module.fail_json(msg="Something fatal happened")
|
||||
|
||||
There are also other useful functions in the module class, such as module.sha1(path). See
|
||||
There are also other useful functions in the module class, such as module.md5(path). See
|
||||
lib/ansible/module_common.py in the source checkout for implementation details.
|
||||
|
||||
Again, modules developed this way are best tested with the hacking/test-module script in the git
|
||||
@ -292,7 +282,7 @@ will evaluate to True when check mode is enabled. For example::
|
||||
)
|
||||
|
||||
if module.check_mode:
|
||||
# Check if any changes would be made but don't actually make those changes
|
||||
# Check if any changes would be made by don't actually make those changes
|
||||
module.exit_json(changed=check_if_system_state_would_be_changed())
|
||||
|
||||
Remember that, as module developer, you are responsible for ensuring that no
|
||||
@ -310,7 +300,8 @@ You should also never do this in a module::
|
||||
|
||||
print "some status message"
|
||||
|
||||
Because the output is supposed to be valid JSON.
|
||||
Because the output is supposed to be valid JSON. Except that's not quite true,
|
||||
but we'll get to that later.
|
||||
|
||||
Modules must not output anything on standard error, because the system will merge
|
||||
standard out with standard error and prevent the JSON from parsing. Capturing standard
|
||||
@ -343,7 +334,7 @@ and guidelines:
|
||||
|
||||
* If packaging modules in an RPM, they only need to be installed on the control machine and should be dropped into /usr/share/ansible. This is entirely optional and up to you.
|
||||
|
||||
* Modules must output valid JSON only. The toplevel return type must be a hash (dictionary) although they can be nested. Lists or simple scalar values are not supported, though they can be trivially contained inside a dictionary.
|
||||
* Modules should return JSON or key=value results all on one line. JSON is best if you can do JSON. All return types must be hashes (dictionaries) although they can be nested. Lists or simple scalar values are not supported, though they can be trivially contained inside a dictionary.
|
||||
|
||||
* In the event of failure, a key of 'failed' should be included, along with a string explanation in 'msg'. Modules that raise tracebacks (stacktraces) are generally considered 'poor' modules, though Ansible can deal with these returns and will automatically convert anything unparseable into a failed result. If you are using the AnsibleModule common Python code, the 'failed' element will be included for you automatically when you call 'fail_json'.
|
||||
|
||||
@ -351,6 +342,21 @@ and guidelines:
|
||||
|
||||
* As results from many hosts will be aggregated at once, modules should return only relevant output. Returning the entire contents of a log file is generally bad form.
|
||||
|
||||
.. _module_dev_shorthand:
|
||||
|
||||
Shorthand Vs JSON
|
||||
`````````````````
|
||||
|
||||
To make it easier to write modules in bash and in cases where a JSON
|
||||
module might not be available, it is acceptable for a module to return
|
||||
key=value output all on one line, like this. The Ansible parser
|
||||
will know what to do::
|
||||
|
||||
somekey=1 somevalue=2 rc=3 favcolor=red
|
||||
|
||||
If you're writing a module in Python or Ruby or whatever, though, returning
|
||||
JSON is probably the simplest way to go.
|
||||
|
||||
.. _module_documenting:
|
||||
|
||||
Documenting Your Module
|
||||
@ -371,7 +377,7 @@ See an example documentation string in the checkout under `examples/DOCUMENTATIO
|
||||
|
||||
Include it in your module file like this::
|
||||
|
||||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
# Copyright header....
|
||||
|
||||
DOCUMENTATION = '''
|
||||
@ -381,13 +387,13 @@ Include it in your module file like this::
|
||||
# ... snip ...
|
||||
'''
|
||||
|
||||
The ``description``, and ``notes`` fields
|
||||
support formatting with some special macros.
|
||||
The ``description``, and ``notes`` fields
|
||||
support formatting with some special macros.
|
||||
|
||||
These formatting functions are ``U()``, ``M()``, ``I()``, and ``C()``
|
||||
for URL, module, italic, and constant-width respectively. It is suggested
|
||||
to use ``C()`` for file and option names, and ``I()`` when referencing
|
||||
parameters; module names should be specified as ``M(module)``.
|
||||
parameters; module names should be specifies as ``M(module)``.
|
||||
|
||||
Examples (which typically contain colons, quotes, etc.) are difficult
|
||||
to format with YAML, so these must be
|
||||
@ -401,30 +407,6 @@ like this::
|
||||
The EXAMPLES section, just like the documentation section, is required in
|
||||
all module pull requests for new modules.
|
||||
|
||||
The RETURN section documents what the module returns. For each value returned,
|
||||
provide a ``description``, in what circumstances the value is ``returned``,
|
||||
the ``type`` of the value and a ``sample``. For example, from
|
||||
the ``copy`` module::
|
||||
|
||||
RETURN = '''
|
||||
dest:
|
||||
description: destination file/path
|
||||
returned: success
|
||||
type: string
|
||||
sample: "/path/to/file.txt"
|
||||
src:
|
||||
description: source file used for the copy on the target machine
|
||||
returned: changed
|
||||
type: string
|
||||
sample: "/home/httpd/.ansible/tmp/ansible-tmp-1423796390.97-147729857856000/source"
|
||||
md5sum:
|
||||
description: md5 checksum of the file after running copy
|
||||
returned: when supported
|
||||
type: string
|
||||
sample: "2a5aeecc61dc98c4d780b14b330e3282"
|
||||
...
|
||||
'''
|
||||
|
||||
.. _module_dev_testing:
|
||||
|
||||
Building & Testing
|
||||
@ -441,159 +423,20 @@ built and appear in the 'docsite/' directory.
|
||||
|
||||
.. tip::
|
||||
|
||||
You can set the environment variable ANSIBLE_KEEP_REMOTE_FILES=1 on the controlling host to prevent ansible from
|
||||
You can use ANSIBLE_KEEP_REMOTE_FILES=1 to prevent ansible from
|
||||
deleting the remote files so you can debug your module.
|
||||
|
||||
.. _module_contribution:
|
||||
|
||||
Module Paths
|
||||
````````````
|
||||
|
||||
If you are having trouble getting your module "found" by ansible, be
|
||||
sure it is in the ``ANSIBLE_LIBRARY`` environment variable.
|
||||
|
||||
If you have a fork of one of the ansible module projects, do something like this::
|
||||
|
||||
ANSIBLE_LIBRARY=~/ansible-modules-core:~/ansible-modules-extras
|
||||
|
||||
And this will make the items in your fork be loaded ahead of what ships with Ansible. Just be sure
|
||||
to make sure you're not reporting bugs on versions from your fork!
|
||||
|
||||
To be safe, if you're working on a variant on something in Ansible's normal distribution, it's not
|
||||
a bad idea to give it a new name while you are working on it, to be sure you know you're pulling
|
||||
your version.
|
||||
|
||||
Getting Your Module Into Ansible
|
||||
````````````````````````````````
|
||||
Getting Your Module Into Core
|
||||
`````````````````````````````
|
||||
|
||||
High-quality modules with minimal dependencies
|
||||
can be included in Ansible, but modules (just due to the programming
|
||||
can be included in the core, but core modules (just due to the programming
|
||||
preferences of the developers) will need to be implemented in Python and use
|
||||
the AnsibleModule common code, and should generally use consistent arguments with the rest of
|
||||
the program. Stop by the mailing list to inquire about requirements if you like, and submit
|
||||
a github pull request to the `extras <https://github.com/ansible/ansible-modules-extras>`_ project.
|
||||
Included modules will ship with ansible, and also have a chance to be promoted to 'core' status, which
|
||||
gives them slightly higher development priority (though they'll work in exactly the same way).
|
||||
|
||||
Module checklist
|
||||
````````````````
|
||||
|
||||
* The shebang should always be #!/usr/bin/python, this allows ansible_python_interpreter to work
|
||||
* Documentation: Make sure it exists
|
||||
* `required` should always be present, be it true or false
|
||||
* If `required` is false you need to document `default`, even if the default is 'None' (which is the default if no parameter is supplied). Make sure default parameter in docs matches default parameter in code.
|
||||
* `default` is not needed for `required: true`
|
||||
* Remove unnecessary doc like `aliases: []` or `choices: []`
|
||||
* The version is not a float number and value the current development version
|
||||
* Verify that arguments in doc and module spec dict are identical
|
||||
* For password / secret arguments no_log=True should be set
|
||||
* Requirements should be documented, using the `requirements=[]` field
|
||||
* Author should be set, name and github id at least
|
||||
* Made use of U() for urls, C() for files and options, I() for params, M() for modules?
|
||||
* GPL 3 License header
|
||||
* Does module use check_mode? Could it be modified to use it? Document it
|
||||
* Examples: make sure they are reproducible
|
||||
* Return: document the return structure of the module
|
||||
* Exceptions: The module must handle them. (exceptions are bugs)
|
||||
* Give out useful messages on what you were doing and you can add the exception message to that.
|
||||
* Avoid catchall exceptions, they are not very useful unless the underlying API gives very good error messages pertaining the attempted action.
|
||||
* The module must not use sys.exit() --> use fail_json() from the module object
|
||||
* Import custom packages in try/except and handled with fail_json() in main() e.g.::
|
||||
|
||||
try:
|
||||
import foo
|
||||
HAS_LIB=True
|
||||
except:
|
||||
HAS_LIB=False
|
||||
|
||||
* The return structure should be consistent, even if NA/None are used for keys normally returned under other options.
|
||||
* Are module actions idempotent? If not document in the descriptions or the notes
|
||||
* Import module snippets `from ansible.module_utils.basic import *` at the bottom, conserves line numbers for debugging.
|
||||
* Call your :func:`main` from a conditional so that it would be possible to
|
||||
test them in the future example::
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
* Try to normalize parameters with other modules, you can have aliases for when user is more familiar with underlying API name for the option
|
||||
* Being pep8 compliant is nice, but not a requirement. Specifically, the 80 column limit now hinders readability more that it improves it
|
||||
* Avoid '`action`/`command`', they are imperative and not declarative, there are other ways to express the same thing
|
||||
* Do not add `list` or `info` state options to an existing module - create a new `_facts` module.
|
||||
* If you are asking 'how can I have a module execute other modules' ... you want to write a role
|
||||
* Return values must be able to be serialized as json via the python stdlib
|
||||
json library. basic python types (strings, int, dicts, lists, etc) are
|
||||
serializable. A common pitfall is to try returning an object via
|
||||
exit_json(). Instead, convert the fields you need from the object into the
|
||||
fields of a dictionary and return the dictionary.
|
||||
* When fetching URLs, please use either fetch_url or open_url from ansible.module_utils.urls
|
||||
rather than urllib2; urllib2 does not natively verify TLS certificates and so is insecure for https.
|
||||
|
||||
Windows modules checklist
|
||||
`````````````````````````
|
||||
* Favour native powershell and .net ways of doing things over calls to COM libraries or calls to native executables which may or may not be present in all versions of windows
|
||||
* modules are in powershell (.ps1 files) but the docs reside in same name python file (.py)
|
||||
* look at ansible/lib/ansible/module_utils/powershell.ps1 for common code, avoid duplication
|
||||
* Ansible uses strictmode version 2.0 so be sure to test with that enabled
|
||||
* start with::
|
||||
|
||||
#!powershell
|
||||
|
||||
then::
|
||||
<GPL header>
|
||||
then::
|
||||
# WANT_JSON
|
||||
# POWERSHELL_COMMON
|
||||
|
||||
then, to parse all arguments into a variable modules generally use::
|
||||
$params = Parse-Args $args
|
||||
|
||||
* Arguments:
|
||||
* Try and use state present and state absent like other modules
|
||||
* You need to check that all your mandatory args are present. You can do this using the builtin Get-AnsibleParam function.
|
||||
* Required arguments::
|
||||
$package = Get-AnsibleParam -obj $params -name name -failifempty $true
|
||||
* Required arguments with name validation::
|
||||
$state = Get-AnsibleParam -obj $params -name "State" -ValidateSet "Present","Absent" -resultobj $resultobj -failifempty $true
|
||||
* Optional arguments with name validation::
|
||||
$state = Get-AnsibleParam -obj $params -name "State" -default "Present" -ValidateSet "Present","Absent"
|
||||
* the If "FailIfEmpty" is true, the resultobj parameter is used to specify the object returned to fail-json. You can also override the default message
|
||||
using $emptyattributefailmessage (for missing required attributes) and $ValidateSetErrorMessage (for attribute validation errors)
|
||||
* Look at existing modules for more examples of argument checking.
|
||||
|
||||
* Results
|
||||
* The result object should allways contain an attribute called changed set to either $true or $false
|
||||
* Create your result object like this::
|
||||
|
||||
$result = New-Object psobject @{
|
||||
changed = $false
|
||||
other_result_attribute = $some_value
|
||||
};
|
||||
|
||||
If all is well, exit with a
|
||||
Exit-Json $result
|
||||
|
||||
* Ensure anything you return, including errors can be converted to json.
|
||||
* Be aware that because exception messages could contain almost anything.
|
||||
* ConvertTo-Json will fail if it encounters a trailing \ in a string.
|
||||
* If all is not well use Fail-Json to exit.
|
||||
|
||||
* Have you tested for powershell 3.0 and 4.0 compliance?
|
||||
|
||||
Deprecating and making module aliases
|
||||
``````````````````````````````````````
|
||||
|
||||
Starting in 1.8 you can deprecate modules by renaming them with a preceding _, i.e. old_cloud.py to
|
||||
_old_cloud.py, This will keep the module available but hide it from the primary docs and listing.
|
||||
|
||||
You can also rename modules and keep an alias to the old name by using a symlink that starts with _.
|
||||
This example allows the stat module to be called with fileinfo, making the following examples equivalent
|
||||
|
||||
EXAMPLES = '''
|
||||
ln -s stat.py _fileinfo.py
|
||||
ansible -m stat -a "path=/tmp" localhost
|
||||
ansible -m fileinfo -a "path=/tmp" localhost
|
||||
'''
|
||||
|
||||
a github pull request to the main project.
|
||||
|
||||
.. seealso::
|
||||
|
||||
@ -603,11 +446,11 @@ This example allows the stat module to be called with fileinfo, making the follo
|
||||
Learn about developing plugins
|
||||
:doc:`developing_api`
|
||||
Learn about the Python API for playbook and task execution
|
||||
`GitHub Core modules directory <https://github.com/ansible/ansible-modules-core/tree/devel>`_
|
||||
`Github modules directory <https://github.com/ansible/ansible/tree/devel/library>`_
|
||||
Browse source of core modules
|
||||
`Github Extras modules directory <https://github.com/ansible/ansible-modules-extras/tree/devel>`_
|
||||
Browse source of extras modules.
|
||||
`Mailing List <http://groups.google.com/group/ansible-devel>`_
|
||||
Development mailing list
|
||||
`irc.freenode.net <http://irc.freenode.net>`_
|
||||
#ansible IRC chat channel
|
||||
|
||||
|
||||
|
||||
@ -19,9 +19,9 @@ in playbooks and with /usr/bin/ansible to decide how you want to talk to remote
|
||||
are covered in the :doc:`intro_getting_started` section. Should you want to extend Ansible to support other transports (SNMP? Message bus?
|
||||
Carrier Pigeon?) it's as simple as copying the format of one of the existing modules and dropping it into the connection plugins
|
||||
directory. The value of 'smart' for a connection allows selection of paramiko or openssh based on system capabilities, and chooses
|
||||
'ssh' if OpenSSH supports ControlPersist, in Ansible 1.2.1 and later. Previous versions did not support 'smart'.
|
||||
'ssh' if OpenSSH supports ControlPersist, in Ansible 1.2.1 an later. Previous versions did not support 'smart'.
|
||||
|
||||
More documentation on writing connection plugins is pending, though you can jump into `lib/ansible/plugins/connection <https://github.com/ansible/ansible/tree/devel/lib/ansible/plugins/connection>`_ and figure things out pretty easily.
|
||||
More documentation on writing connection plugins is pending, though you can jump into `lib/ansible/runner/connection_plugins <https://github.com/ansible/ansible/tree/devel/lib/ansible/runner/connection_plugins>`_ and figure things out pretty easily.
|
||||
|
||||
.. _developing_lookup_plugins:
|
||||
|
||||
@ -30,7 +30,7 @@ Lookup Plugins
|
||||
|
||||
Language constructs like "with_fileglob" and "with_items" are implemented via lookup plugins. Just like other plugin types, you can write your own.
|
||||
|
||||
More documentation on writing lookup plugins is pending, though you can jump into `lib/ansible/plugins/lookup <https://github.com/ansible/ansible/tree/devel/lib/ansible/plugins/lookup>`_ and figure
|
||||
More documentation on writing connection plugins is pending, though you can jump into `lib/ansible/runner/lookup_plugins <https://github.com/ansible/ansible/tree/devel/lib/ansible/runner/lookup_plugins>`_ and figure
|
||||
things out pretty easily.
|
||||
|
||||
.. _developing_vars_plugins:
|
||||
@ -42,7 +42,7 @@ Playbook constructs like 'host_vars' and 'group_vars' work via 'vars' plugins.
|
||||
data into ansible runs that did not come from an inventory, playbook, or command line. Note that variables
|
||||
can also be returned from inventory, so in most cases, you won't need to write or understand vars_plugins.
|
||||
|
||||
More documentation on writing vars plugins is pending, though you can jump into `lib/ansible/inventory/vars_plugins <https://github.com/ansible/ansible/tree/devel/lib/ansible/inventory/vars_plugins>`_ and figure
|
||||
More documentation on writing connection plugins is pending, though you can jump into `lib/ansible/inventory/vars_plugins <https://github.com/ansible/ansible/tree/devel/lib/ansible/inventory/vars_plugins>`_ and figure
|
||||
things out pretty easily.
|
||||
|
||||
If you find yourself wanting to write a vars_plugin, it's more likely you should write an inventory script instead.
|
||||
@ -54,7 +54,7 @@ Filter Plugins
|
||||
|
||||
If you want more Jinja2 filters available in a Jinja2 template (filters like to_yaml and to_json are provided by default), they can be extended by writing a filter plugin. Most of the time, when someone comes up with an idea for a new filter they would like to make available in a playbook, we'll just include them in 'core.py' instead.
|
||||
|
||||
Jump into `lib/ansible/plugins/filter <https://github.com/ansible/ansible/tree/devel/lib/ansible/plugins/filter>`_ for details.
|
||||
Jump into `lib/ansible/runner/filter_plugins/ <https://github.com/ansible/ansible/tree/devel/lib/ansible/runner/filter_plugins>`_ for details.
|
||||
|
||||
.. _developing_callbacks:
|
||||
|
||||
@ -68,17 +68,17 @@ Callbacks are one of the more interesting plugin types. Adding additional callb
|
||||
Examples
|
||||
++++++++
|
||||
|
||||
Example callbacks are shown in `lib/ansible/plugins/callback <https://github.com/ansible/ansible/tree/devel/lib/ansible/plugins/callback>`_.
|
||||
Example callbacks are shown in `plugins/callbacks <https://github.com/ansible/ansible/tree/devel/plugins/callbacks>`_.
|
||||
|
||||
The `log_plays
|
||||
<https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/callback/log_plays.py>`_
|
||||
<https://github.com/ansible/ansible/blob/devel/plugins/callbacks/log_plays.py>`_
|
||||
callback is an example of how to intercept playbook events to a log
|
||||
file, and the `mail
|
||||
<https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/callback/mail.py>`_
|
||||
<https://github.com/ansible/ansible/blob/devel/plugins/callbacks/mail.py>`_
|
||||
callback sends email when playbooks complete.
|
||||
|
||||
The `osx_say
|
||||
<https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/callback/osx_say.py>`_
|
||||
<https://github.com/ansible/ansible/blob/devel/plugins/callbacks/osx_say.py>`_
|
||||
callback provided is particularly entertaining -- it will respond with
|
||||
computer synthesized speech on OS X in relation to playbook events,
|
||||
and is guaranteed to entertain and/or annoy coworkers.
|
||||
@ -88,7 +88,7 @@ and is guaranteed to entertain and/or annoy coworkers.
|
||||
Configuring
|
||||
+++++++++++
|
||||
|
||||
To activate a callback drop it in a callback directory as configured in :ref:`ansible.cfg <callback_plugins>`.
|
||||
To active a callback drop it in a callback directory as configured in :ref:`ansible.cfg <callback_plugins>`.
|
||||
|
||||
.. _callback_development:
|
||||
|
||||
|
||||
@ -1,186 +0,0 @@
|
||||
Helping Testing PRs
|
||||
```````````````````
|
||||
|
||||
If you're a developer, one of the most valuable things you can do is look at the github issues list and help fix bugs. We almost always prioritize bug fixing over
|
||||
feature development, so clearing bugs out of the way is one of the best things you can do.
|
||||
|
||||
Even if you're not a developer, helping test pull requests for bug fixes and features is still immensely valuable.
|
||||
|
||||
This goes for testing new features as well as testing bugfixes.
|
||||
|
||||
In many cases, code should add tests that prove it works but that's not ALWAYS possible and tests are not always comprehensive, especially when a user doesn't have access
|
||||
to a wide variety of platforms, or that is using an API or web service.
|
||||
|
||||
In these cases, live testing against real equipment can be more valuable than automation that runs against simulated interfaces.
|
||||
In any case, things should always be tested manually the first time too.
|
||||
|
||||
Thankfully helping test ansible is pretty straightforward, assuming you are already used to how ansible works.
|
||||
|
||||
Get Started with A Source Checkout
|
||||
++++++++++++++++++++++++++++++++++
|
||||
|
||||
You can do this by checking out ansible, making a test branch off the main one, merging a GitHub issue, testing,
|
||||
and then commenting on that particular issue on GitHub. Here's how:
|
||||
|
||||
.. note::
|
||||
Testing source code from GitHub pull requests sent to us does have some inherent risk, as the source code
|
||||
sent may have mistakes or malicious code that could have a negative impact on your system. We recommend
|
||||
doing all testing on a virtual machine, whether a cloud instance, or locally. Some users like Vagrant
|
||||
or Docker for this, but they are optional. It is also useful to have virtual machines of different Linux or
|
||||
other flavors, since some features (apt vs. yum, for example) are specific to those OS versions.
|
||||
|
||||
First, you will need to configure your testing environment with the necessary tools required to run our test
|
||||
suites. You will need at least::
|
||||
|
||||
git
|
||||
python-nosetests (sometimes named python-nose)
|
||||
python-passlib
|
||||
python-mock
|
||||
|
||||
If you want to run the full integration test suite you'll also need the following packages installed::
|
||||
|
||||
svn
|
||||
hg
|
||||
python-pip
|
||||
gem
|
||||
|
||||
Second, if you haven't already, clone the Ansible source code from GitHub::
|
||||
|
||||
git clone https://github.com/ansible/ansible.git --recursive
|
||||
cd ansible/
|
||||
|
||||
.. note::
|
||||
If you have previously forked the repository on GitHub, you could also clone it from there.
|
||||
|
||||
.. note::
|
||||
If updating your repo for testing something module related, use "git rebase origin/devel" and then "git submodule update" to fetch
|
||||
the latest development versions of modules. Skipping the "git submodule update" step will result in versions that will be stale.
|
||||
|
||||
Activating The Source Checkout
|
||||
++++++++++++++++++++++++++++++
|
||||
|
||||
The Ansible source includes a script that allows you to use Ansible directly from source without requiring a
|
||||
full installation, that is frequently used by developers on Ansible.
|
||||
|
||||
Simply source it (to use the Linux/Unix terminology) to begin using it immediately::
|
||||
|
||||
source ./hacking/env-setup
|
||||
|
||||
This script modifies the PYTHONPATH enviromnent variables (along with a few other things), which will be temporarily
|
||||
set as long as your shell session is open.
|
||||
|
||||
If you'd like your testing environment to always use the latest source, you could call the command from startup scripts (for example,
|
||||
`.bash_profile`).
|
||||
|
||||
Finding A Pull Request and Checking It Out On A Branch
|
||||
++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
Next, find the pull request you'd like to test and make note of the line at the top which describes the source
|
||||
and destination repositories. It will look something like this::
|
||||
|
||||
Someuser wants to merge 1 commit into ansible:devel from someuser:feature_branch_name
|
||||
|
||||
.. note::
|
||||
It is important that the PR request target be ansible:devel, as we do not accept pull requests into any other branch.
|
||||
Dot releases are cherry-picked manually by ansible staff.
|
||||
|
||||
The username and branch at the end are the important parts, which will be turned into git commands as follows::
|
||||
|
||||
git checkout -b testing_PRXXXX devel
|
||||
git pull https://github.com/someuser/ansible.git feature_branch_name
|
||||
|
||||
The first command creates and switches to a new branch named testing_PRXXXX, where the XXXX is the actual issue number associated
|
||||
with the pull request (for example, 1234). This branch is based on the devel branch. The second command pulls the new code from the
|
||||
users feature branch into the newly created branch.
|
||||
|
||||
.. note::
|
||||
If the GitHub user interface shows that the pull request will not merge cleanly, we do not recommend proceeding if you
|
||||
are not somewhat familiar with git and coding, as you will have to resolve a merge conflict. This is the responsibility of
|
||||
the original pull request contributor.
|
||||
|
||||
.. note::
|
||||
Some users do not create feature branches, which can cause problems when they have multiple, un-related commits in
|
||||
their version of `devel`. If the source looks like `someuser:devel`, make sure there is only one commit listed on
|
||||
the pull request.
|
||||
|
||||
For Those About To Test, We Salute You
|
||||
++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
At this point, you should be ready to begin testing!
|
||||
|
||||
If the PR is a bug-fix pull request, the first things to do are to run the suite of unit and integration tests, to ensure
|
||||
the pull request does not break current functionality::
|
||||
|
||||
# Unit Tests
|
||||
make tests
|
||||
|
||||
# Integration Tests
|
||||
cd test/integration
|
||||
make
|
||||
|
||||
.. note::
|
||||
Ansible does provide integration tests for cloud-based modules as well, however we do not recommend using them for some users
|
||||
due to the associated costs from the cloud providers. As such, typically it's better to run specific parts of the integration battery
|
||||
and skip these tests.
|
||||
|
||||
Integration tests aren't the end all beat all - in many cases what is fixed might not *HAVE* a test, so determining if it works means
|
||||
checking the functionality of the system and making sure it does what it said it would do.
|
||||
|
||||
Pull requests for bug-fixes should reference the bug issue number they are fixing.
|
||||
|
||||
We encourage users to provide playbook examples for bugs that show how to reproduce the error, and these playbooks should be used to verify the bugfix does resolve
|
||||
the issue if available. You may wish to also do your own review to poke the corners of the change.
|
||||
|
||||
Since some reproducers can be quite involved, you might wish to create a testing directory with the issue # as a sub-
|
||||
directory to keep things organized::
|
||||
|
||||
mkdir -p testing/XXXX # where XXXX is again the issue # for the original issue or PR
|
||||
cd testing/XXXX
|
||||
<create files or git clone example playbook repo>
|
||||
|
||||
While it should go without saying, be sure to read any playbooks before you run them. VMs help with running untrusted content greatly,
|
||||
though a playbook could still do something to your computing resources that you'd rather not like.
|
||||
|
||||
Once the files are in place, you can run the provided playbook (if there is one) to test the functionality::
|
||||
|
||||
ansible-playbook -vvv playbook_name.yml
|
||||
|
||||
If there's not a playbook, you may have to copy and paste playbook snippets or run a ad-hoc command that was pasted in.
|
||||
|
||||
Our issue template also included sections for "Expected Output" and "Actual Output", which should be used to gauge the output
|
||||
from the provided examples.
|
||||
|
||||
If the pull request resolves the issue, please leave a comment on the pull request, showing the following information:
|
||||
|
||||
* "Works for me!"
|
||||
* The output from `ansible --version`.
|
||||
|
||||
In some cases, you may wish to share playbook output from the test run as well.
|
||||
|
||||
Example!::
|
||||
|
||||
Works for me! Tested on `Ansible 1.7.1`. I verified this on CentOS 6.5 and also Ubuntu 14.04.
|
||||
|
||||
If the PR does not resolve the issue, or if you see any failures from the unit/integration tests, just include that output instead::
|
||||
|
||||
This doesn't work for me.
|
||||
|
||||
When I ran this my toaster started making loud noises!
|
||||
|
||||
Output from the toaster looked like this:
|
||||
|
||||
```
|
||||
BLARG
|
||||
StrackTrace
|
||||
RRRARRGGG
|
||||
```
|
||||
|
||||
When you are done testing a feature branch, you can remove it with the following command::
|
||||
|
||||
git branch -D someuser-feature_branch_name
|
||||
|
||||
We understand some users may be inexperienced with git, or other aspects of the above procedure, so feel free to stop by ansible-devel
|
||||
list for questions and we'd be happy to help answer them.
|
||||
|
||||
|
||||
|
||||
@ -3,32 +3,18 @@ Frequently Asked Questions
|
||||
|
||||
Here are some commonly-asked questions and their answers.
|
||||
|
||||
|
||||
.. _set_environment:
|
||||
|
||||
How can I set the PATH or any other environment variable for a task or entire playbook?
|
||||
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
Setting environment variables can be done with the `environment` keyword. It can be used at task or play level::
|
||||
|
||||
environment:
|
||||
PATH: "{{ ansible_env.PATH }}:/thingy/bin"
|
||||
SOME: value
|
||||
|
||||
|
||||
.. _users_and_ports:
|
||||
|
||||
How do I handle different machines needing different user accounts or ports to log in with?
|
||||
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
Setting inventory variables in the inventory file is the easiest way.
|
||||
|
||||
.. include:: ansible_ssh_changes_note.rst
|
||||
|
||||
For instance, suppose these hosts have different usernames and ports::
|
||||
|
||||
[webservers]
|
||||
asdf.example.com ansible_port=5000 ansible_user=alice
|
||||
jkl.example.com ansible_port=5001 ansible_user=bob
|
||||
asdf.example.com ansible_ssh_port=5000 ansible_ssh_user=alice
|
||||
jkl.example.com ansible_ssh_port=5001 ansible_ssh_user=bob
|
||||
|
||||
You can also dictate the connection type to be used, if you want::
|
||||
|
||||
@ -58,37 +44,6 @@ consider managing from a Fedora or openSUSE client even though you are managing
|
||||
We keep paramiko as the default as if you are first installing Ansible on an EL box, it offers a better experience
|
||||
for new users.
|
||||
|
||||
.. _use_ssh_jump_hosts:
|
||||
|
||||
How do I configure a jump host to access servers that I have no direct access to?
|
||||
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
With Ansible 2, you can set a `ProxyCommand` in the
|
||||
`ansible_ssh_common_args` inventory variable. Any arguments specified in
|
||||
this variable are added to the sftp/scp/ssh command line when connecting
|
||||
to the relevant host(s). Consider the following inventory group::
|
||||
|
||||
[gatewayed]
|
||||
foo ansible_host=192.0.2.1
|
||||
bar ansible_host=192.0.2.2
|
||||
|
||||
You can create `group_vars/gatewayed.yml` with the following contents::
|
||||
|
||||
ansible_ssh_common_args: '-o ProxyCommand="ssh -W %h:%p -q user@gateway.example.com"'
|
||||
|
||||
Ansible will append these arguments to the command line when trying to
|
||||
connect to any hosts in the group `gatewayed`. (These arguments are used
|
||||
in addition to any `ssh_args` from `ansible.cfg`, so you do not need to
|
||||
repeat global `ControlPersist` settings in `ansible_ssh_common_args`.)
|
||||
|
||||
Note that `ssh -W` is available only with OpenSSH 5.4 or later. With
|
||||
older versions, it's necessary to execute `nc %h:%p` or some equivalent
|
||||
command on the bastion host.
|
||||
|
||||
With earlier versions of Ansible, it was necessary to configure a
|
||||
suitable `ProxyCommand` for one or more hosts in `~/.ssh/config`,
|
||||
or globally by setting `ssh_args` in `ansible.cfg`.
|
||||
|
||||
.. _ec2_cloud_performance:
|
||||
|
||||
How do I speed up management inside EC2?
|
||||
@ -157,16 +112,7 @@ Ansible by default gathers "facts" about the machines under management, and thes
|
||||
|
||||
ansible -m setup hostname
|
||||
|
||||
This will print out a dictionary of all of the facts that are available for that particular host. You might want to pipe the output to a pager.
|
||||
|
||||
.. _browse_inventory_vars:
|
||||
|
||||
How do I see all the inventory vars defined for my host?
|
||||
++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
You can see the resulting vars you define in inventory running the following command::
|
||||
|
||||
ansible -m debug -a "var=hostvars['hostname']" localhost
|
||||
This will print out a dictionary of all of the facts that are available for that particular host.
|
||||
|
||||
.. _host_loops:
|
||||
|
||||
@ -220,10 +166,10 @@ Anyway, here's the trick::
|
||||
{{ hostvars[groups['webservers'][0]]['ansible_eth0']['ipv4']['address'] }}
|
||||
|
||||
Notice how we're pulling out the hostname of the first machine of the webservers group. If you are doing this in a template, you
|
||||
could use the Jinja2 '#set' directive to simplify this, or in a playbook, you could also use set_fact::
|
||||
could use the Jinja2 '#set' directive to simplify this, or in a playbook, you could also use set_fact:
|
||||
|
||||
- set_fact: headnode={{ groups[['webservers'][0]] }}
|
||||
|
||||
|
||||
- debug: msg={{ hostvars[headnode].ansible_eth0.ipv4.address }}
|
||||
|
||||
Notice how we interchanged the bracket syntax for dots -- that can be done anywhere.
|
||||
@ -233,7 +179,7 @@ Notice how we interchanged the bracket syntax for dots -- that can be done anywh
|
||||
How do I copy files recursively onto a target host?
|
||||
+++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
The "copy" module has a recursive parameter, though if you want to do something more efficient for a large number of files, take a look at the "synchronize" module instead, which wraps rsync. See the module index for info on both of these modules.
|
||||
The "copy" module has a recursive parameter, though if you want to do something more efficient for a large number of files, take a look at the "synchronize" module instead, which wraps rsync. See the module index for info on both of these modules.
|
||||
|
||||
.. _shell_env:
|
||||
|
||||
@ -271,16 +217,14 @@ password hashing library is installed.
|
||||
|
||||
Once the library is ready, SHA512 password values can then be generated as follows::
|
||||
|
||||
python -c "from passlib.hash import sha512_crypt; import getpass; print sha512_crypt.encrypt(getpass.getpass())"
|
||||
python -c "from passlib.hash import sha512_crypt; print sha512_crypt.encrypt('<password>')"
|
||||
|
||||
.. _commercial_support:
|
||||
|
||||
Can I get training on Ansible or find commercial support?
|
||||
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
Yes! See our `services page <http://www.ansible.com/services>`_ for information on our services and training offerings. Support is also included with :doc:`tower`. Email `info@ansible.com <mailto:info@ansible.com>`_ for further details.
|
||||
|
||||
We also offer free web-based training classes on a regular basis. See our `webinar page <http://www.ansible.com/webinars-training>`_ for more info on upcoming webinars.
|
||||
Yes! See `our Guru offering <http://www.ansible.com/ansible-guru>`_ for online support, and support is also included with :doc:`tower`. You can also read our `service page <http://www.ansible.com/ansible-services>`_ and email `info@ansible.com <mailto:info@ansible.com>`_ for further details.
|
||||
|
||||
.. _web_interface:
|
||||
|
||||
@ -306,23 +250,6 @@ If you would like to keep secret data in your Ansible content and still share it
|
||||
|
||||
.. _i_dont_see_my_question:
|
||||
|
||||
In Ansible 1.8 and later, if you have a task that you don't want to show the results or command given to it when using -v (verbose) mode, the following task or playbook attribute can be useful::
|
||||
|
||||
- name: secret task
|
||||
shell: /usr/bin/do_something --value={{ secret_value }}
|
||||
no_log: True
|
||||
|
||||
This can be used to keep verbose output but hide sensitive information from others who would otherwise like to be able to see the output.
|
||||
|
||||
The no_log attribute can also apply to an entire play::
|
||||
|
||||
- hosts: all
|
||||
no_log: True
|
||||
|
||||
Though this will make the play somewhat difficult to debug. It's recommended that this
|
||||
be applied to single tasks only, once a playbook is completed.
|
||||
|
||||
|
||||
I don't see my question here
|
||||
++++++++++++++++++++++++++++
|
||||
|
||||
@ -336,7 +263,7 @@ Please see the section below for a link to IRC and the Google Group, where you c
|
||||
An introduction to playbooks
|
||||
:doc:`playbooks_best_practices`
|
||||
Best practices advice
|
||||
`User Mailing List <http://groups.google.com/group/ansible-project>`_
|
||||
`User Mailing List <http://groups.google.com/group/ansible-devel>`_
|
||||
Have a question? Stop by the google group!
|
||||
`irc.freenode.net <http://irc.freenode.net>`_
|
||||
#ansible IRC chat channel
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user