mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
merged
This commit is contained in:
commit
7a76fcb159
991 changed files with 76319 additions and 12784 deletions
4
.coveragerc
Normal file
4
.coveragerc
Normal file
|
@ -0,0 +1,4 @@
|
|||
[report]
|
||||
omit =
|
||||
*/python?.?/*
|
||||
*/site-packages/nose/*
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
CHANGELOG.md merge=union
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -42,6 +42,7 @@ deb-build
|
|||
credentials.yml
|
||||
# test output
|
||||
.coverage
|
||||
.tox
|
||||
results.xml
|
||||
coverage.xml
|
||||
/test/units/cover-html
|
||||
|
|
20
.gitmodules
vendored
20
.gitmodules
vendored
|
@ -1,16 +1,12 @@
|
|||
[submodule "lib/ansible/modules/core"]
|
||||
path = lib/ansible/modules/core
|
||||
url = https://github.com/ansible/ansible-modules-core.git
|
||||
branch = devel
|
||||
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.git
|
||||
branch = devel
|
||||
[submodule "v2/ansible/modules/core"]
|
||||
path = v2/ansible/modules/core
|
||||
url = https://github.com/ansible/ansible-modules-core.git
|
||||
branch = devel
|
||||
[submodule "v2/ansible/modules/extras"]
|
||||
path = v2/ansible/modules/extras
|
||||
url = https://github.com/ansible/ansible-modules-extras.git
|
||||
branch = devel
|
||||
url = https://github.com/ansible/ansible-modules-extras
|
||||
[submodule "v1/ansible/modules/core"]
|
||||
path = v1/ansible/modules/core
|
||||
url = https://github.com/ansible/ansible-modules-core
|
||||
[submodule "v1/ansible/modules/extras"]
|
||||
path = v1/ansible/modules/extras
|
||||
url = https://github.com/ansible/ansible-modules-extras
|
||||
|
|
18
.travis.yml
Normal file
18
.travis.yml
Normal file
|
@ -0,0 +1,18 @@
|
|||
sudo: false
|
||||
language: python
|
||||
env:
|
||||
- TOXENV=py26
|
||||
- TOXENV=py27
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- deadsnakes
|
||||
packages:
|
||||
- python2.4
|
||||
install:
|
||||
- pip install tox PyYAML Jinja2 sphinx
|
||||
script:
|
||||
- tox
|
||||
#- make -C docsite all
|
||||
after_success:
|
||||
- coveralls
|
361
CHANGELOG.md
361
CHANGELOG.md
|
@ -1,7 +1,335 @@
|
|||
Ansible Changes By Release
|
||||
==========================
|
||||
|
||||
## 1.8 "You Really Got Me" - Active Development
|
||||
## 2.0 "TBD" - ACTIVE DEVELOPMENT
|
||||
|
||||
Major Changes:
|
||||
* Introducing the new block/rescue/always directives, allow for making task blocks and introducing exception like semantics
|
||||
* New stratergy plugins, allow to control the flow of execution of tasks per play, the default will be the same as before
|
||||
* Improved error handling, now you get much more detailed parser messages. General exception handling and display has been revamped.
|
||||
* Task includes now get evaluated during execution, end behaviour will be the same but it now allows for more dynamic includes and options.
|
||||
* First feature of the more dynamic includes is that with_ loops are now usable with them.
|
||||
* callback, connection and lookup plugin APIs have changed, some will require modification to work with new version
|
||||
* callbacks are now shipped in the active directory and don't need to be copied, just whitelisted in ansible.cfg
|
||||
* Many API changes, this will break those currently using it directly, 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 inhertited by the contained,
|
||||
this allows for new feautures to automatically be settable at all levels, previouslly we had to manually code this
|
||||
* Many more tests, 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)
|
||||
* 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
|
||||
|
||||
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)
|
||||
|
||||
New Modules:
|
||||
* amazon: ec2_ami_copy
|
||||
* amazon: ec2_ami_find
|
||||
* amazon: ec2_eni
|
||||
* amazon: ec2_eni_facts
|
||||
* amazon: ec2_vpc_net
|
||||
* amazon: ec2_win_password
|
||||
* amazon: elasticache_subnet_group
|
||||
* amazon: iam
|
||||
* amazon: iam_policy
|
||||
* amazon: route53_zone
|
||||
* amazon: sts_assume_role
|
||||
* amazon: s3_logging
|
||||
* bundler
|
||||
* centurylink: clc_publicip
|
||||
* circonus_annotation
|
||||
* consul
|
||||
* consul_acl
|
||||
* consul_kv
|
||||
* consul_session
|
||||
* cloudtrail
|
||||
* cloudstack: cs_account
|
||||
* cloudstack: cs_affinitygroup
|
||||
* cloudstack: cs_facts
|
||||
* cloudstack: cs_firewall
|
||||
* cloudstack: cs_iso
|
||||
* cloudstack: cs_instance
|
||||
* cloudstack: cs_instancegroup
|
||||
* 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_vmsnapshot
|
||||
* datadog_monitor
|
||||
* dpkg_selections
|
||||
* elasticsearch_plugin
|
||||
* expect
|
||||
* find
|
||||
* hall
|
||||
* maven_artifact
|
||||
* openstack: os_ironic
|
||||
* openstack: os_ironic_node
|
||||
* openstack: os_client_config
|
||||
* openstack: os_floating_ip
|
||||
* openstack: os_image
|
||||
* openstack: os_network
|
||||
* openstack: os_nova_flavor
|
||||
* openstack: os_object
|
||||
* 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_volume
|
||||
* osx_defaults
|
||||
* pear
|
||||
* profitbricks: profitbricks
|
||||
* proxmox
|
||||
* proxmox_template
|
||||
* puppet
|
||||
* pushover
|
||||
* pushbullet
|
||||
* 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
|
||||
* sensu_check
|
||||
* sensu_subscription
|
||||
* vertica_configuration
|
||||
* vertica_facts
|
||||
* vertica_role
|
||||
* vertica_schema
|
||||
* vertica_user
|
||||
* vmware_datacenter
|
||||
* vsphere_copy
|
||||
* webfaction_app
|
||||
* webfaction_db
|
||||
* webfaction_domain
|
||||
* webfaction_mailbox
|
||||
* webfaction_site
|
||||
* win_environment
|
||||
* win_scheduled_task
|
||||
* win_iis_virtualdirectory
|
||||
* win_iis_webapplication
|
||||
* win_iis_webapppool
|
||||
* win_iis_webbinding
|
||||
* win_iis_website
|
||||
* win_regedit
|
||||
* win_unzip
|
||||
* zabbix_host
|
||||
* zabbix_hostmacro
|
||||
* zabbix_screen
|
||||
|
||||
New Inventory scripts:
|
||||
* cloudstack
|
||||
* fleetctl
|
||||
* serf
|
||||
|
||||
Other Notable Changes:
|
||||
|
||||
## 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:
|
||||
|
||||
|
@ -17,6 +345,9 @@ Major changes:
|
|||
* 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:
|
||||
|
||||
|
@ -33,6 +364,7 @@ New Modules:
|
|||
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.
|
||||
|
@ -58,7 +390,18 @@ Some other notable changes:
|
|||
* 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
|
||||
* 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 ...
|
||||
|
||||
|
@ -69,7 +412,7 @@ And various other bug fixes and improvements ...
|
|||
- 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 honored during the templating of files.
|
||||
- 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
|
||||
|
@ -112,7 +455,7 @@ New Modules:
|
|||
Other notable changes:
|
||||
|
||||
* Security fixes
|
||||
- Prevent the use of lookups when using legaxy "{{ }}" syntax around variables and with_* loops.
|
||||
- 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.
|
||||
|
@ -256,7 +599,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 precendence for system facts, to ensure that inventory variables take precedence over any facts that may be set on a host.
|
||||
- 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.
|
||||
|
||||
## 1.5.3 "Love Walks In" - March 13, 2014
|
||||
|
||||
|
@ -291,7 +634,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 unavailble, the yum module will automatically attempt to install yum-utils
|
||||
* if repoquery is unavailable, 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
|
||||
|
||||
|
@ -754,7 +1097,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 connecton URIs
|
||||
* libvirt module can now work with multiple different libvirt connection URIs
|
||||
* fix for postgresql password escaping
|
||||
* unicode fix for shlex.split in some cases
|
||||
* apt module upgrade logic improved
|
||||
|
@ -789,7 +1132,7 @@ the variable is still registered for the host, with the attribute skipped: True.
|
|||
* 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 unpriveledged users
|
||||
* pip works better when sudoing from unprivileged 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
|
||||
|
@ -959,7 +1302,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 indvidual task
|
||||
* can override "connection:" on an individual 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
|
||||
|
|
|
@ -1,11 +1,22 @@
|
|||
##### 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”, or “Docs Pull Request”.
|
||||
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.
|
||||
|
|
12
MANIFEST.in
12
MANIFEST.in
|
@ -1,3 +1,9 @@
|
|||
prune v2
|
||||
prune docsite
|
||||
prune ticket_stubs
|
||||
prune packaging
|
||||
prune test
|
||||
prune hacking
|
||||
include README.md packaging/rpm/ansible.spec COPYING
|
||||
include examples/hosts
|
||||
include examples/ansible.cfg
|
||||
|
@ -7,3 +13,9 @@ recursive-include docs *
|
|||
include Makefile
|
||||
include VERSION
|
||||
include MANIFEST.in
|
||||
include contrib/README.md
|
||||
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
|
||||
|
|
17
Makefile
17
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
|
||||
# make tests ---------------- run the tests (see test/README.md for requirements)
|
||||
# make pyflakes, make pep8 -- source code checks
|
||||
|
||||
########################################################
|
||||
|
@ -34,7 +34,8 @@ 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)
|
||||
VERSION := $(shell cat VERSION | cut -f1 -d' ')
|
||||
RELEASE := $(shell cat VERSION | cut -f2 -d' ')
|
||||
|
||||
# Get the branch information from git
|
||||
ifneq ($(shell which git),)
|
||||
|
@ -52,8 +53,9 @@ DEBUILD_BIN ?= debuild
|
|||
DEBUILD_OPTS = --source-option="-I"
|
||||
DPUT_BIN ?= dput
|
||||
DPUT_OPTS ?=
|
||||
DEB_DATE := $(shell date +"%a, %d %b %Y %T %z")
|
||||
ifeq ($(OFFICIAL),yes)
|
||||
DEB_RELEASE = 1ppa
|
||||
DEB_RELEASE = $(RELEASE)ppa
|
||||
# Sign OFFICIAL builds using 'DEBSIGN_KEYID'
|
||||
# DEBSIGN_KEYID is required when signing
|
||||
ifneq ($(DEBSIGN_KEYID),)
|
||||
|
@ -74,7 +76,7 @@ DEB_DIST ?= unstable
|
|||
RPMSPECDIR= packaging/rpm
|
||||
RPMSPEC = $(RPMSPECDIR)/ansible.spec
|
||||
RPMDIST = $(shell rpm --eval '%{?dist}')
|
||||
RPMRELEASE = 1
|
||||
RPMRELEASE = $(RELEASE)
|
||||
ifneq ($(OFFICIAL),yes)
|
||||
RPMRELEASE = 0.git$(DATE)
|
||||
endif
|
||||
|
@ -93,7 +95,7 @@ NOSETESTS3 ?= nosetests-3.3
|
|||
all: clean python
|
||||
|
||||
tests:
|
||||
PYTHONPATH=./lib $(NOSETESTS) -d -w test/units -v # Could do: --with-coverage --cover-package=ansible
|
||||
PYTHONPATH=./lib $(NOSETESTS) -d -w test/units -v --with-coverage --cover-package=ansible --cover-branches
|
||||
|
||||
newtests:
|
||||
PYTHONPATH=./v2:./lib $(NOSETESTS) -d -w v2/test -v --with-coverage --cover-package=ansible --cover-branches
|
||||
|
@ -134,7 +136,7 @@ clean:
|
|||
@echo "Cleaning up byte compiled python stuff"
|
||||
find . -type f -regex ".*\.py[co]$$" -delete
|
||||
@echo "Cleaning up editor backup files"
|
||||
find . -type f \( -name "*~" -or -name "#*" \) -delete
|
||||
find . -type f \( -name "*~" -or -name "#*" \) |grep -v test/units/inventory_test_data/group_vars/noparse/all.yml~ |xargs -n 1024 -r rm
|
||||
find . -type f \( -name "*.swp" \) -delete
|
||||
@echo "Cleaning up manpage stuff"
|
||||
find ./docs/man -type f -name "*.xml" -delete
|
||||
|
@ -151,6 +153,7 @@ 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
|
||||
|
@ -215,7 +218,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#^$(NAME) (\([^)]*\)) \([^;]*\);#ansible (\1-$(DEB_RELEASE)~$${DIST}) $${DIST};#" deb-build/$${DIST}/$(NAME)-$(VERSION)/debian/changelog ; \
|
||||
sed -ie "s|%VERSION%|$(VERSION)|g;s|%RELEASE%|$(DEB_RELEASE)|;s|%DIST%|$${DIST}|g;s|%DATE%|$(DEB_DATE)|g" deb-build/$${DIST}/$(NAME)-$(VERSION)/debian/changelog ; \
|
||||
done
|
||||
|
||||
deb: debian
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
[![PyPI version](https://badge.fury.io/py/ansible.png)](http://badge.fury.io/py/ansible) [![PyPI downloads](https://pypip.in/d/ansible/badge.png)](https://pypi.python.org/pypi/ansible)
|
||||
[![PyPI version](https://badge.fury.io/py/ansible.png)](http://badge.fury.io/py/ansible)
|
||||
[![PyPI downloads](https://pypip.in/d/ansible/badge.png)](https://pypi.python.org/pypi/ansible)
|
||||
[![Build Status](https://travis-ci.org/ansible/ansible.svg?branch=devel)](https://travis-ci.org/ansible/ansible)
|
||||
|
||||
|
||||
Ansible
|
||||
|
@ -49,8 +51,7 @@ Branch Info
|
|||
Authors
|
||||
=======
|
||||
|
||||
Ansible was created by [Michael DeHaan](github.com/mpdehaan) (michael@ansible.com) and has contributions from over
|
||||
800 users (and growing). Thanks everyone!
|
||||
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)
|
||||
|
||||
|
|
|
@ -4,11 +4,17 @@ Ansible Releases at a Glance
|
|||
Active Development
|
||||
++++++++++++++++++
|
||||
|
||||
1.8 "You Really Got Me" ---- FALL 2014
|
||||
2.0 "TBD" - in progress
|
||||
|
||||
Released
|
||||
++++++++
|
||||
|
||||
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
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
1.8
|
||||
2.0.0 0.0.pre
|
||||
|
|
248
bin/ansible
248
bin/ansible
|
@ -18,187 +18,93 @@
|
|||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
########################################################
|
||||
from __future__ import (absolute_import, 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.runner import Runner
|
||||
import ansible.constants as C
|
||||
from ansible import utils
|
||||
from ansible import errors
|
||||
from ansible import callbacks
|
||||
from ansible import inventory
|
||||
########################################################
|
||||
from ansible.errors import AnsibleError, AnsibleOptionsError, AnsibleParserError
|
||||
from ansible.utils.display import Display
|
||||
|
||||
class Cli(object):
|
||||
''' code behind bin/ansible '''
|
||||
########################################
|
||||
### OUTPUT OF LAST RESORT ###
|
||||
class LastResort(object):
|
||||
def display(self, msg):
|
||||
print(msg, file=sys.stderr)
|
||||
|
||||
# ----------------------------------------------
|
||||
|
||||
def __init__(self):
|
||||
self.stats = callbacks.AggregateStats()
|
||||
self.callbacks = callbacks.CliRunnerCallbacks()
|
||||
if C.DEFAULT_LOAD_CALLBACK_PLUGINS:
|
||||
callbacks.load_callback_plugins()
|
||||
|
||||
# ----------------------------------------------
|
||||
|
||||
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]
|
||||
|
||||
sshpass = None
|
||||
sudopass = None
|
||||
su_pass = None
|
||||
vault_pass = None
|
||||
|
||||
options.ask_pass = options.ask_pass or C.DEFAULT_ASK_PASS
|
||||
# Never ask for an SSH password when we run with local connection
|
||||
if options.connection == "local":
|
||||
options.ask_pass = False
|
||||
options.ask_sudo_pass = options.ask_sudo_pass or C.DEFAULT_ASK_SUDO_PASS
|
||||
options.ask_su_pass = options.ask_su_pass or C.DEFAULT_ASK_SU_PASS
|
||||
options.ask_vault_pass = options.ask_vault_pass or C.DEFAULT_ASK_VAULT_PASS
|
||||
|
||||
(sshpass, sudopass, su_pass, vault_pass) = utils.ask_passwords(ask_pass=options.ask_pass, ask_sudo_pass=options.ask_sudo_pass, ask_su_pass=options.ask_su_pass, ask_vault_pass=options.ask_vault_pass)
|
||||
|
||||
# read vault_pass from a file
|
||||
if not options.ask_vault_pass and options.vault_password_file:
|
||||
vault_pass = utils.read_vault_file(options.vault_password_file)
|
||||
|
||||
inventory_manager = inventory.Inventory(options.inventory, vault_password=vault_pass)
|
||||
if options.subset:
|
||||
inventory_manager.subset(options.subset)
|
||||
hosts = inventory_manager.list_hosts(pattern)
|
||||
if len(hosts) == 0:
|
||||
callbacks.display("No hosts matched", stderr=True)
|
||||
sys.exit(0)
|
||||
|
||||
if options.listhosts:
|
||||
for host in hosts:
|
||||
callbacks.display(' %s' % host)
|
||||
sys.exit(0)
|
||||
|
||||
if ((options.module_name == 'command' or options.module_name == 'shell')
|
||||
and not options.module_args):
|
||||
callbacks.display("No argument passed to %s module" % options.module_name, color='red', stderr=True)
|
||||
sys.exit(1)
|
||||
def error(self, msg, wrap_text=None):
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
cli = Cli()
|
||||
(options, args) = cli.parse()
|
||||
display = LastResort()
|
||||
cli = None
|
||||
me = os.path.basename(sys.argv[0])
|
||||
|
||||
try:
|
||||
(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)
|
||||
display = Display()
|
||||
|
||||
if me == 'ansible-playbook':
|
||||
from ansible.cli.playbook import PlaybookCLI as mycli
|
||||
elif me == 'ansible':
|
||||
from ansible.cli.adhoc import AdHocCLI as mycli
|
||||
elif me == 'ansible-pull':
|
||||
from ansible.cli.pull import PullCLI as mycli
|
||||
elif me == 'ansible-doc':
|
||||
from ansible.cli.doc import DocCLI as mycli
|
||||
elif me == 'ansible-vault':
|
||||
from ansible.cli.vault import VaultCLI as mycli
|
||||
elif me == 'ansible-galaxy':
|
||||
from ansible.cli.galaxy import GalaxyCLI as mycli
|
||||
|
||||
cli = mycli(sys.argv, display=display)
|
||||
if cli:
|
||||
cli.parse()
|
||||
sys.exit(cli.run())
|
||||
else:
|
||||
raise AnsibleError("Program not implemented: %s" % me)
|
||||
|
||||
except AnsibleOptionsError as e:
|
||||
cli.parser.print_help()
|
||||
display.error(str(e), wrap_text=False)
|
||||
sys.exit(5)
|
||||
except AnsibleParserError as e:
|
||||
display.error(str(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(str(e), wrap_text=False)
|
||||
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" % str(e), wrap_text=False)
|
||||
if not have_cli_options or have_cli_options and cli.options.verbosity > 2:
|
||||
display.display("the full traceback was:\n\n%s" % traceback.format_exc())
|
||||
else:
|
||||
display.display("to see the full traceback, use -vvv")
|
||||
sys.exit(250)
|
||||
|
|
295
bin/ansible-doc
295
bin/ansible-doc
|
@ -1,295 +0,0 @@
|
|||
#!/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 = ('.pyc', '.swp', '.bak', '~', '.rpm')
|
||||
|
||||
_ITALIC = re.compile(r"I\(([^)]+)\)")
|
||||
_BOLD = re.compile(r"B\(([^)]+)\)")
|
||||
_MODULE = re.compile(r"M\(([^)]+)\)")
|
||||
_URL = re.compile(r"U\(([^)]+)\)")
|
||||
_CONST = re.compile(r"C\(([^)]+)\)")
|
||||
PAGER = 'less'
|
||||
LESS_OPTS = 'FRSX' # -F (quit-if-one-screen) -R (allow raw ansi control chars)
|
||||
# -S (chop long lines) -X (disable termcap init and de-init)
|
||||
|
||||
def pager_print(text):
|
||||
''' just print text '''
|
||||
print text
|
||||
|
||||
def pager_pipe(text, cmd):
|
||||
''' pipe text through a pager '''
|
||||
if 'LESS' not in os.environ:
|
||||
os.environ['LESS'] = LESS_OPTS
|
||||
try:
|
||||
cmd = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=sys.stdout)
|
||||
cmd.communicate(input=text)
|
||||
except IOError:
|
||||
pass
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
def pager(text):
|
||||
''' find reasonable way to display text '''
|
||||
# this is a much simpler form of what is in pydoc.py
|
||||
if not sys.stdout.isatty():
|
||||
pager_print(text)
|
||||
elif 'PAGER' in os.environ:
|
||||
if sys.platform == 'win32':
|
||||
pager_print(text)
|
||||
else:
|
||||
pager_pipe(text, os.environ['PAGER'])
|
||||
elif hasattr(os, 'system') and os.system('(less) 2> /dev/null') == 0:
|
||||
pager_pipe(text, 'less')
|
||||
else:
|
||||
pager_print(text)
|
||||
|
||||
def tty_ify(text):
|
||||
|
||||
t = _ITALIC.sub("`" + r"\1" + "'", text) # I(word) => `word'
|
||||
t = _BOLD.sub("*" + r"\1" + "*", t) # B(word) => *word*
|
||||
t = _MODULE.sub("[" + r"\1" + "]", t) # M(word) => [word]
|
||||
t = _URL.sub(r"\1", t) # U(word) => word
|
||||
t = _CONST.sub("`" + r"\1" + "'", t) # C(word) => `word'
|
||||
|
||||
return t
|
||||
|
||||
def get_man_text(doc):
|
||||
|
||||
opt_indent=" "
|
||||
text = []
|
||||
text.append("> %s\n" % doc['module'].upper())
|
||||
|
||||
desc = " ".join(doc['description'])
|
||||
|
||||
text.append("%s\n" % textwrap.fill(tty_ify(desc), initial_indent=" ", subsequent_indent=" "))
|
||||
|
||||
if 'option_keys' in doc and len(doc['option_keys']) > 0:
|
||||
text.append("Options (= is mandatory):\n")
|
||||
|
||||
for o in sorted(doc['option_keys']):
|
||||
opt = doc['options'][o]
|
||||
|
||||
if opt.get('required', False):
|
||||
opt_leadin = "="
|
||||
else:
|
||||
opt_leadin = "-"
|
||||
|
||||
text.append("%s %s" % (opt_leadin, o))
|
||||
|
||||
desc = " ".join(opt['description'])
|
||||
|
||||
if 'choices' in opt:
|
||||
choices = ", ".join(str(i) for i in opt['choices'])
|
||||
desc = desc + " (Choices: " + choices + ")"
|
||||
if 'default' in opt:
|
||||
default = str(opt['default'])
|
||||
desc = desc + " [Default: " + default + "]"
|
||||
text.append("%s\n" % textwrap.fill(tty_ify(desc), initial_indent=opt_indent,
|
||||
subsequent_indent=opt_indent))
|
||||
|
||||
if 'notes' in doc and len(doc['notes']) > 0:
|
||||
notes = " ".join(doc['notes'])
|
||||
text.append("Notes:%s\n" % textwrap.fill(tty_ify(notes), initial_indent=" ",
|
||||
subsequent_indent=opt_indent))
|
||||
|
||||
|
||||
if 'requirements' in doc and doc['requirements'] is not None and len(doc['requirements']) > 0:
|
||||
req = ", ".join(doc['requirements'])
|
||||
text.append("Requirements:%s\n" % textwrap.fill(tty_ify(req), initial_indent=" ",
|
||||
subsequent_indent=opt_indent))
|
||||
|
||||
if 'examples' in doc and len(doc['examples']) > 0:
|
||||
text.append("Example%s:\n" % ('' if len(doc['examples']) < 2 else 's'))
|
||||
for ex in doc['examples']:
|
||||
text.append("%s\n" % (ex['code']))
|
||||
|
||||
if 'plainexamples' in doc and doc['plainexamples'] is not None:
|
||||
text.append(doc['plainexamples'])
|
||||
text.append('')
|
||||
|
||||
return "\n".join(text)
|
||||
|
||||
|
||||
def get_snippet_text(doc):
|
||||
|
||||
text = []
|
||||
desc = tty_ify(" ".join(doc['short_description']))
|
||||
text.append("- name: %s" % (desc))
|
||||
text.append(" action: %s" % (doc['module']))
|
||||
|
||||
for o in sorted(doc['options'].keys()):
|
||||
opt = doc['options'][o]
|
||||
desc = tty_ify(" ".join(opt['description']))
|
||||
|
||||
if opt.get('required', False):
|
||||
s = o + "="
|
||||
else:
|
||||
s = o
|
||||
|
||||
text.append(" %-20s # %s" % (s, desc))
|
||||
text.append('')
|
||||
|
||||
return "\n".join(text)
|
||||
|
||||
def get_module_list_text(module_list):
|
||||
text = []
|
||||
for module in sorted(set(module_list)):
|
||||
|
||||
if module in module_docs.BLACKLIST_MODULES:
|
||||
continue
|
||||
|
||||
filename = utils.plugins.module_finder.find_plugin(module)
|
||||
|
||||
if filename is None:
|
||||
continue
|
||||
if filename.endswith(".ps1"):
|
||||
continue
|
||||
if os.path.isdir(filename):
|
||||
continue
|
||||
|
||||
try:
|
||||
doc, plainexamples = module_docs.get_docstring(filename)
|
||||
desc = tty_ify(doc.get('short_description', '?'))
|
||||
if len(desc) > 55:
|
||||
desc = desc + '...'
|
||||
text.append("%-20s %-60.60s" % (module, desc))
|
||||
except:
|
||||
traceback.print_exc()
|
||||
sys.stderr.write("ERROR: module %s has a documentation error formatting or is missing documentation\n" % module)
|
||||
return "\n".join(text)
|
||||
|
||||
def main():
|
||||
|
||||
p = optparse.OptionParser(
|
||||
version=version("%prog"),
|
||||
usage='usage: %prog [options] [module...]',
|
||||
description='Show Ansible module documentation',
|
||||
)
|
||||
|
||||
p.add_option("-M", "--module-path",
|
||||
action="store",
|
||||
dest="module_path",
|
||||
default=MODULEDIR,
|
||||
help="Ansible modules/ directory")
|
||||
p.add_option("-l", "--list",
|
||||
action="store_true",
|
||||
default=False,
|
||||
dest='list_dir',
|
||||
help='List available modules')
|
||||
p.add_option("-s", "--snippet",
|
||||
action="store_true",
|
||||
default=False,
|
||||
dest='show_snippet',
|
||||
help='Show playbook snippet for specified module(s)')
|
||||
p.add_option('-v', action='version', help='Show version number and exit')
|
||||
|
||||
(options, args) = p.parse_args()
|
||||
|
||||
if options.module_path is not None:
|
||||
for i in options.module_path.split(os.pathsep):
|
||||
utils.plugins.module_finder.add_directory(i)
|
||||
|
||||
if options.list_dir:
|
||||
# list all modules
|
||||
paths = utils.plugins.module_finder._get_paths()
|
||||
module_list = []
|
||||
for path in paths:
|
||||
# os.system("ls -C %s" % (path))
|
||||
if os.path.isdir(path):
|
||||
for module in os.listdir(path):
|
||||
if any(module.endswith(x) for x in BLACKLIST_EXTS):
|
||||
continue
|
||||
module_list.append(module)
|
||||
|
||||
pager(get_module_list_text(module_list))
|
||||
sys.exit()
|
||||
|
||||
if len(args) == 0:
|
||||
p.print_help()
|
||||
|
||||
def print_paths(finder):
|
||||
''' Returns a string suitable for printing of the search path '''
|
||||
|
||||
# Uses a list to get the order right
|
||||
ret = []
|
||||
for i in finder._get_paths():
|
||||
if i not in ret:
|
||||
ret.append(i)
|
||||
return os.pathsep.join(ret)
|
||||
|
||||
text = ''
|
||||
for module in args:
|
||||
|
||||
filename = utils.plugins.module_finder.find_plugin(module)
|
||||
if filename is None:
|
||||
sys.stderr.write("module %s not found in %s\n" % (module,
|
||||
print_paths(utils.plugins.module_finder)))
|
||||
continue
|
||||
|
||||
if any(filename.endswith(x) for x in BLACKLIST_EXTS):
|
||||
continue
|
||||
|
||||
try:
|
||||
doc, plainexamples = module_docs.get_docstring(filename)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
sys.stderr.write("ERROR: module %s has a documentation error formatting or is missing documentation\n" % module)
|
||||
continue
|
||||
|
||||
if doc is not None:
|
||||
|
||||
all_keys = []
|
||||
for (k,v) in doc['options'].iteritems():
|
||||
all_keys.append(k)
|
||||
all_keys = sorted(all_keys)
|
||||
doc['option_keys'] = all_keys
|
||||
|
||||
doc['filename'] = filename
|
||||
doc['docuri'] = doc['module'].replace('_', '-')
|
||||
doc['now_date'] = datetime.date.today().strftime('%Y-%m-%d')
|
||||
doc['plainexamples'] = plainexamples
|
||||
|
||||
if options.show_snippet:
|
||||
text += get_snippet_text(doc)
|
||||
else:
|
||||
text += get_man_text(doc)
|
||||
else:
|
||||
# this typically means we couldn't even parse the docstring, not just that the YAML is busted,
|
||||
# probably a quoting issue.
|
||||
sys.stderr.write("ERROR: module %s missing documentation (or could not parse documentation)\n" % module)
|
||||
pager(text)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
1
bin/ansible-doc
Symbolic link
1
bin/ansible-doc
Symbolic link
|
@ -0,0 +1 @@
|
|||
ansible
|
|
@ -1,901 +0,0 @@
|
|||
#!/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 subprocess
|
||||
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
|
||||
import ansible.utils
|
||||
from ansible.errors import AnsibleError
|
||||
|
||||
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.')
|
||||
parser.add_option(
|
||||
'--offline', dest='offline', default=False, action='store_true',
|
||||
help="Don't query the galaxy API when creating roles")
|
||||
elif action == "install":
|
||||
parser.set_usage("usage: %prog install [options] [-r FILE | role_name(s)[,version] | scm+role_repo_url[,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). Specify role as format: 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
|
||||
|
||||
#-------------------------------------------------------------------------------------
|
||||
# scm repo utility functions
|
||||
#-------------------------------------------------------------------------------------
|
||||
|
||||
def scm_archive_role(scm, role_url, role_version, role_name):
|
||||
if scm not in ['hg', 'git']:
|
||||
print "- scm %s is not currently supported" % scm
|
||||
return False
|
||||
tempdir = tempfile.mkdtemp()
|
||||
clone_cmd = [scm, 'clone', role_url, role_name]
|
||||
with open('/dev/null', 'w') as devnull:
|
||||
try:
|
||||
print "- executing: %s" % " ".join(clone_cmd)
|
||||
popen = subprocess.Popen(clone_cmd, cwd=tempdir, stdout=devnull, stderr=devnull)
|
||||
except:
|
||||
raise AnsibleError("error executing: %s" % " ".join(clone_cmd))
|
||||
rc = popen.wait()
|
||||
if rc != 0:
|
||||
print "- command %s failed" % ' '.join(clone_cmd)
|
||||
print " in directory %s" % tempdir
|
||||
return False
|
||||
|
||||
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.tar')
|
||||
if scm == 'hg':
|
||||
archive_cmd = ['hg', 'archive', '--prefix', "%s/" % role_name]
|
||||
if role_version:
|
||||
archive_cmd.extend(['-r', role_version])
|
||||
archive_cmd.append(temp_file.name)
|
||||
if scm == 'git':
|
||||
archive_cmd = ['git', 'archive', '--prefix=%s/' % role_name, '--output=%s' % temp_file.name]
|
||||
if role_version:
|
||||
archive_cmd.append(role_version)
|
||||
else:
|
||||
archive_cmd.append('HEAD')
|
||||
|
||||
with open('/dev/null', 'w') as devnull:
|
||||
print "- executing: %s" % " ".join(archive_cmd)
|
||||
popen = subprocess.Popen(archive_cmd, cwd=os.path.join(tempdir, role_name),
|
||||
stderr=devnull, stdout=devnull)
|
||||
rc = popen.wait()
|
||||
if rc != 0:
|
||||
print "- command %s failed" % ' '.join(archive_cmd)
|
||||
print " in directory %s" % tempdir
|
||||
return False
|
||||
|
||||
shutil.rmtree(tempdir)
|
||||
|
||||
return temp_file.name
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------------
|
||||
# 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
|
||||
if '://' in role_name:
|
||||
archive_url = role_name
|
||||
else:
|
||||
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:
|
||||
if role_filename.endswith('.gz'):
|
||||
role_tar_file = tarfile.open(role_filename, "r:gz")
|
||||
else:
|
||||
role_tar_file = tarfile.open(role_filename, "r")
|
||||
# verify the role's meta file
|
||||
meta_file = None
|
||||
members = role_tar_file.getmembers()
|
||||
# next find the metadata file
|
||||
for member in members:
|
||||
if "/meta/main.yml" in member.name:
|
||||
meta_file = member
|
||||
break
|
||||
if not meta_file:
|
||||
print "- error: this role does not appear to have a meta/main.yml file."
|
||||
return False
|
||||
else:
|
||||
try:
|
||||
meta_file_data = yaml.safe_load(role_tar_file.extractfile(meta_file))
|
||||
except:
|
||||
print "- error: this role does not appear to have a valid meta/main.yml file."
|
||||
return False
|
||||
|
||||
# we strip off the top-level directory for all of the files contained within
|
||||
# the tar file here, since the default is 'github_repo-target', and change it
|
||||
# to the specified role's name
|
||||
role_path = os.path.join(get_opt(options, 'roles_path'), role_name)
|
||||
role_path = os.path.expanduser(role_path)
|
||||
print "- extracting %s to %s" % (role_name, role_path)
|
||||
try:
|
||||
if os.path.exists(role_path):
|
||||
if not os.path.isdir(role_path):
|
||||
print "- error: the specified roles path exists and is not a directory."
|
||||
return False
|
||||
elif not get_opt(options, "force", False):
|
||||
print "- error: the specified role %s appears to already exist. Use --force to replace it." % role_name
|
||||
return False
|
||||
else:
|
||||
# using --force, remove the old path
|
||||
if not remove_role(role_name, options):
|
||||
print "- error: %s doesn't appear to contain a role." % role_path
|
||||
print " please remove this directory manually if you really want to put the role here."
|
||||
return False
|
||||
else:
|
||||
os.makedirs(role_path)
|
||||
|
||||
# now we do the actual extraction to the role_path
|
||||
for member in members:
|
||||
# we only extract files, and remove any relative path
|
||||
# bits that might be in the file for security purposes
|
||||
# and drop the leading directory, as mentioned above
|
||||
if member.isreg():
|
||||
parts = member.name.split("/")[1:]
|
||||
final_parts = []
|
||||
for part in parts:
|
||||
if part != '..' and '~' not in part and '$' not in part:
|
||||
final_parts.append(part)
|
||||
member.name = os.path.join(*final_parts)
|
||||
role_tar_file.extract(member, role_path)
|
||||
|
||||
# write out the install info file for later use
|
||||
write_galaxy_install_info(role_name, role_version, options)
|
||||
except OSError, e:
|
||||
print "- error: you do not have permission to modify files in %s" % role_path
|
||||
return False
|
||||
|
||||
# return the parsed yaml metadata
|
||||
print "- %s was installed successfully" % role_name
|
||||
return meta_file_data
|
||||
|
||||
#-------------------------------------------------------------------------------------
|
||||
# Action functions
|
||||
#-------------------------------------------------------------------------------------
|
||||
|
||||
def execute_init(args, options, parser):
|
||||
"""
|
||||
Executes the init action, which creates the skeleton framework
|
||||
of a role that complies with the galaxy metadata format.
|
||||
"""
|
||||
|
||||
init_path = get_opt(options, 'init_path', './')
|
||||
api_server = get_opt(options, "api_server", "galaxy.ansible.com")
|
||||
force = get_opt(options, 'force', False)
|
||||
offline = get_opt(options, 'offline', False)
|
||||
|
||||
if not offline:
|
||||
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 " 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 = []
|
||||
if not offline:
|
||||
platforms = api_get_list(api_server, "platforms") or []
|
||||
categories = []
|
||||
if not offline:
|
||||
categories = api_get_list(api_server, "categories") or []
|
||||
|
||||
# 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)
|
||||
roles_path = get_opt(options, "roles_path")
|
||||
|
||||
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)
|
||||
|
||||
roles_done = []
|
||||
if role_file:
|
||||
f = open(role_file, 'r')
|
||||
if role_file.endswith('.yaml') or role_file.endswith('.yml'):
|
||||
roles_left = map(ansible.utils.role_yaml_parse, yaml.safe_load(f))
|
||||
else:
|
||||
# roles listed in a file, one per line
|
||||
roles_left = map(ansible.utils.role_spec_parse, 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 = map(ansible.utils.role_spec_parse, args)
|
||||
|
||||
while len(roles_left) > 0:
|
||||
# query the galaxy API for the role data
|
||||
role_data = None
|
||||
role = roles_left.pop(0)
|
||||
role_src = role.get("src")
|
||||
role_scm = role.get("scm")
|
||||
role_path = role.get("path")
|
||||
|
||||
if role_path:
|
||||
options.roles_path = role_path
|
||||
else:
|
||||
options.roles_path = roles_path
|
||||
|
||||
if os.path.isfile(role_src):
|
||||
# installing a local tar.gz
|
||||
tmp_file = role_src
|
||||
else:
|
||||
if role_scm:
|
||||
# create tar file from scm url
|
||||
tmp_file = scm_archive_role(role_scm, role_src, role.get("version"), role.get("name"))
|
||||
elif '://' in role_src:
|
||||
# just download a URL - version will probably be in the URL
|
||||
tmp_file = fetch_role(role_src, None, None, options)
|
||||
else:
|
||||
# installing from galaxy
|
||||
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)
|
||||
|
||||
role_data = api_lookup_role_by_name(api_server, role_src)
|
||||
if not role_data:
|
||||
print "- sorry, %s was not found on %s." % (role_src, api_server)
|
||||
exit_without_ignore(options)
|
||||
continue
|
||||
|
||||
role_versions = api_fetch_role_related(api_server, 'versions', role_data['id'])
|
||||
if "version" not in role or 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'
|
||||
elif role['version'] != 'master':
|
||||
if role_versions and role["version"] not in [a.get('name', None) for a in role_versions]:
|
||||
print 'role is %s' % role
|
||||
print "- the specified version (%s) was not found in the list of available versions (%s)." % (role['version'], role_versions)
|
||||
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_src, role["version"], role_data, options)
|
||||
installed = False
|
||||
if tmp_file:
|
||||
installed = install_role(role.get("name"), role.get("version"), tmp_file, options)
|
||||
# we're done with the temp file, clean it up
|
||||
if tmp_file != role_src:
|
||||
os.unlink(tmp_file)
|
||||
# install dependencies, if we want them
|
||||
if not no_deps and installed:
|
||||
if not role_data:
|
||||
role_data = get_role_metadata(role.get("name"), options)
|
||||
role_dependencies = role_data['dependencies']
|
||||
else:
|
||||
role_dependencies = role_data['summary_fields']['dependencies'] # api_fetch_role_related(api_server, 'dependencies', role_data['id'])
|
||||
for dep in role_dependencies:
|
||||
if isinstance(dep, basestring):
|
||||
dep = ansible.utils.role_spec_parse(dep)
|
||||
else:
|
||||
dep = ansible.utils.role_yaml_parse(dep)
|
||||
if not get_role_metadata(dep["name"], options):
|
||||
if dep not in roles_left:
|
||||
print '- adding dependency: %s' % dep["name"]
|
||||
roles_left.append(dep)
|
||||
else:
|
||||
print '- dependency %s already pending installation.' % dep["name"]
|
||||
else:
|
||||
print '- dependency %s is already installed, skipping.' % dep["name"]
|
||||
if not tmp_file or not installed:
|
||||
print "- %s was NOT installed successfully." % role.get("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
bin/ansible-galaxy
Symbolic link
1
bin/ansible-galaxy
Symbolic link
|
@ -0,0 +1 @@
|
|||
ansible
|
|
@ -1,330 +0,0 @@
|
|||
#!/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
|
||||
|
||||
# Augment PYTHONPATH to find Python modules relative to this file path
|
||||
# This is so that we can find the modules when running from a local checkout
|
||||
# installed as editable with `pip install -e ...` or `python setup.py develop`
|
||||
local_module_path = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), '..', 'lib')
|
||||
)
|
||||
sys.path.append(local_module_path)
|
||||
|
||||
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")
|
||||
parser.add_option('--flush-cache', dest='flush_cache', action='store_true',
|
||||
help="clear the fact cache")
|
||||
|
||||
options, args = parser.parse_args(args)
|
||||
|
||||
if len(args) == 0:
|
||||
parser.print_help(file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# su and sudo command line arguments need to be mutually exclusive
|
||||
if (options.su or options.su_user or options.ask_su_pass) and \
|
||||
(options.sudo or options.sudo_user or options.ask_sudo_pass):
|
||||
parser.error("Sudo arguments ('--sudo', '--sudo-user', and '--ask-sudo-pass') "
|
||||
"and su arguments ('-su', '--su-user', and '--ask-su-pass') are "
|
||||
"mutually exclusive")
|
||||
|
||||
if (options.ask_vault_pass and options.vault_password_file):
|
||||
parser.error("--ask-vault-pass and --vault-password-file are mutually exclusive")
|
||||
|
||||
sshpass = None
|
||||
sudopass = None
|
||||
su_pass = None
|
||||
vault_pass = None
|
||||
|
||||
options.ask_vault_pass = options.ask_vault_pass or C.DEFAULT_ASK_VAULT_PASS
|
||||
|
||||
if options.listhosts or options.syntax or options.listtasks:
|
||||
(_, _, _, vault_pass) = utils.ask_passwords(ask_vault_pass=options.ask_vault_pass)
|
||||
else:
|
||||
options.ask_pass = options.ask_pass or C.DEFAULT_ASK_PASS
|
||||
# Never ask for an SSH password when we run with local connection
|
||||
if options.connection == "local":
|
||||
options.ask_pass = False
|
||||
options.ask_sudo_pass = options.ask_sudo_pass or C.DEFAULT_ASK_SUDO_PASS
|
||||
options.ask_su_pass = options.ask_su_pass or C.DEFAULT_ASK_SU_PASS
|
||||
(sshpass, sudopass, su_pass, vault_pass) = utils.ask_passwords(ask_pass=options.ask_pass, ask_sudo_pass=options.ask_sudo_pass, ask_su_pass=options.ask_su_pass, ask_vault_pass=options.ask_vault_pass)
|
||||
options.sudo_user = options.sudo_user or C.DEFAULT_SUDO_USER
|
||||
options.su_user = options.su_user or C.DEFAULT_SU_USER
|
||||
|
||||
# read vault_pass from a file
|
||||
if not options.ask_vault_pass and options.vault_password_file:
|
||||
vault_pass = utils.read_vault_file(options.vault_password_file)
|
||||
|
||||
extra_vars = {}
|
||||
for extra_vars_opt in options.extra_vars:
|
||||
if extra_vars_opt.startswith("@"):
|
||||
# Argument is a YAML file (JSON is a subset of YAML)
|
||||
extra_vars = utils.combine_vars(extra_vars, utils.parse_yaml_from_file(extra_vars_opt[1:], vault_password=vault_pass))
|
||||
elif extra_vars_opt and extra_vars_opt[0] in '[{':
|
||||
# Arguments as YAML
|
||||
extra_vars = utils.combine_vars(extra_vars, utils.parse_yaml(extra_vars_opt))
|
||||
else:
|
||||
# Arguments as Key-value
|
||||
extra_vars = utils.combine_vars(extra_vars, utils.parse_kv(extra_vars_opt))
|
||||
|
||||
only_tags = options.tags.split(",")
|
||||
skip_tags = options.skip_tags
|
||||
if options.skip_tags is not None:
|
||||
skip_tags = options.skip_tags.split(",")
|
||||
|
||||
for playbook in args:
|
||||
if not os.path.exists(playbook):
|
||||
raise errors.AnsibleError("the playbook: %s could not be found" % playbook)
|
||||
if not (os.path.isfile(playbook) or stat.S_ISFIFO(os.stat(playbook).st_mode)):
|
||||
raise errors.AnsibleError("the playbook: %s does not appear to be a file" % playbook)
|
||||
|
||||
inventory = ansible.inventory.Inventory(options.inventory, vault_password=vault_pass)
|
||||
inventory.subset(options.subset)
|
||||
if len(inventory.list_hosts()) == 0:
|
||||
raise errors.AnsibleError("provided hosts list is empty")
|
||||
|
||||
# run all playbooks specified on the command line
|
||||
for playbook in args:
|
||||
|
||||
stats = callbacks.AggregateStats()
|
||||
playbook_cb = callbacks.PlaybookCallbacks(verbose=utils.VERBOSITY)
|
||||
if options.step:
|
||||
playbook_cb.step = options.step
|
||||
if options.start_at:
|
||||
playbook_cb.start_at = options.start_at
|
||||
runner_cb = callbacks.PlaybookRunnerCallbacks(stats, verbose=utils.VERBOSITY)
|
||||
|
||||
pb = ansible.playbook.PlayBook(
|
||||
playbook=playbook,
|
||||
module_path=options.module_path,
|
||||
inventory=inventory,
|
||||
forks=options.forks,
|
||||
remote_user=options.remote_user,
|
||||
remote_pass=sshpass,
|
||||
callbacks=playbook_cb,
|
||||
runner_callbacks=runner_cb,
|
||||
stats=stats,
|
||||
timeout=options.timeout,
|
||||
transport=options.connection,
|
||||
sudo=options.sudo,
|
||||
sudo_user=options.sudo_user,
|
||||
sudo_pass=sudopass,
|
||||
extra_vars=extra_vars,
|
||||
private_key_file=options.private_key_file,
|
||||
only_tags=only_tags,
|
||||
skip_tags=skip_tags,
|
||||
check=options.check,
|
||||
diff=options.diff,
|
||||
su=options.su,
|
||||
su_pass=su_pass,
|
||||
su_user=options.su_user,
|
||||
vault_password=vault_pass,
|
||||
force_handlers=options.force_handlers
|
||||
)
|
||||
|
||||
if options.flush_cache:
|
||||
display(callbacks.banner("FLUSHING FACT CACHE"))
|
||||
pb.SETUP_CACHE.flush()
|
||||
|
||||
if options.listhosts or options.listtasks or options.syntax:
|
||||
print ''
|
||||
print 'playbook: %s' % playbook
|
||||
print ''
|
||||
playnum = 0
|
||||
for (play_ds, play_basedir) in zip(pb.playbook, pb.play_basedirs):
|
||||
playnum += 1
|
||||
play = ansible.playbook.Play(pb, play_ds, play_basedir,
|
||||
vault_password=pb.vault_password)
|
||||
label = play.name
|
||||
hosts = pb.inventory.list_hosts(play.hosts)
|
||||
|
||||
# Filter all tasks by given tags
|
||||
if pb.only_tags != 'all':
|
||||
if options.subset and not hosts:
|
||||
continue
|
||||
matched_tags, unmatched_tags = play.compare_tags(pb.only_tags)
|
||||
|
||||
# Remove skipped tasks
|
||||
matched_tags = matched_tags - set(pb.skip_tags)
|
||||
|
||||
unmatched_tags.discard('all')
|
||||
unknown_tags = ((set(pb.only_tags) | set(pb.skip_tags)) -
|
||||
(matched_tags | unmatched_tags))
|
||||
|
||||
if unknown_tags:
|
||||
continue
|
||||
|
||||
if options.listhosts:
|
||||
print ' play #%d (%s): host count=%d' % (playnum, label, len(hosts))
|
||||
for host in hosts:
|
||||
print ' %s' % host
|
||||
|
||||
if options.listtasks:
|
||||
print ' play #%d (%s):' % (playnum, label)
|
||||
|
||||
for task in play.tasks():
|
||||
if (set(task.tags).intersection(pb.only_tags) and not
|
||||
set(task.tags).intersection(pb.skip_tags)):
|
||||
if getattr(task, 'name', None) is not None:
|
||||
# meta tasks have no names
|
||||
print ' %s' % task.name
|
||||
if options.listhosts or options.listtasks:
|
||||
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
bin/ansible-playbook
Symbolic link
1
bin/ansible-playbook
Symbolic link
|
@ -0,0 +1 @@
|
|||
ansible
|
236
bin/ansible-pull
236
bin/ansible-pull
|
@ -1,236 +0,0 @@
|
|||
#!/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
|
||||
from ansible import inventory
|
||||
|
||||
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")
|
||||
|
||||
# Attempt to use the inventory passed in as an argument
|
||||
# It might not yet have been downloaded so use localhost if note
|
||||
if not options.inventory or not os.path.exists(options.inventory):
|
||||
inv_opts = 'localhost,'
|
||||
else:
|
||||
inv_opts = options.inventory
|
||||
limit_opts = 'localhost:%s:127.0.0.1' % hostname
|
||||
repo_opts = "name=%s dest=%s" % (options.url, options.dest)
|
||||
|
||||
if VERBOSITY == 0:
|
||||
base_opts = '-c local --limit "%s"' % limit_opts
|
||||
elif VERBOSITY > 0:
|
||||
debug_level = ''.join([ "v" for x in range(0, VERBOSITY) ])
|
||||
base_opts = '-%s -c local --limit "%s"' % (debug_level, limit_opts)
|
||||
|
||||
if options.checkout:
|
||||
repo_opts += ' version=%s' % options.checkout
|
||||
path = utils.plugins.module_finder.find_plugin(options.module_name)
|
||||
if path is None:
|
||||
sys.stderr.write("module '%s' not found.\n" % options.module_name)
|
||||
return 1
|
||||
cmd = 'ansible localhost -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
bin/ansible-pull
Symbolic link
1
bin/ansible-pull
Symbolic link
|
@ -0,0 +1 @@
|
|||
ansible
|
|
@ -1,233 +0,0 @@
|
|||
#!/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-vault is a script that encrypts/decrypts YAML files. See
|
||||
# http://docs.ansible.com/playbooks_vault.html for more details.
|
||||
|
||||
#__requires__ = ['ansible']
|
||||
#import pkg_resources
|
||||
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import ansible.constants as C
|
||||
|
||||
from ansible import utils
|
||||
from ansible import errors
|
||||
from ansible.utils.vault import VaultEditor
|
||||
|
||||
from optparse import OptionParser
|
||||
|
||||
#-------------------------------------------------------------------------------------
|
||||
# Utility functions for parsing actions/options
|
||||
#-------------------------------------------------------------------------------------
|
||||
|
||||
VALID_ACTIONS = ("create", "decrypt", "edit", "encrypt", "rekey", "view")
|
||||
|
||||
def build_option_parser(action):
|
||||
"""
|
||||
Builds an option parser object based on the action
|
||||
the user wants to execute.
|
||||
"""
|
||||
|
||||
usage = "usage: %%prog [%s] [--help] [options] file_name" % "|".join(VALID_ACTIONS)
|
||||
epilog = "\nSee '%s <command> --help' for more information on a specific command.\n\n" % os.path.basename(sys.argv[0])
|
||||
OptionParser.format_epilog = lambda self, formatter: self.epilog
|
||||
parser = OptionParser(usage=usage, epilog=epilog)
|
||||
|
||||
if not action:
|
||||
parser.print_help()
|
||||
sys.exit()
|
||||
|
||||
# options for all actions
|
||||
#parser.add_option('-c', '--cipher', dest='cipher', default="AES256", help="cipher to use")
|
||||
parser.add_option('--debug', dest='debug', action="store_true", help="debug")
|
||||
parser.add_option('--vault-password-file', dest='password_file',
|
||||
help="vault password file", default=C.DEFAULT_VAULT_PASSWORD_FILE)
|
||||
|
||||
# options specific to actions
|
||||
if action == "create":
|
||||
parser.set_usage("usage: %prog create [options] file_name")
|
||||
elif action == "decrypt":
|
||||
parser.set_usage("usage: %prog decrypt [options] file_name")
|
||||
elif action == "edit":
|
||||
parser.set_usage("usage: %prog edit [options] file_name")
|
||||
elif action == "view":
|
||||
parser.set_usage("usage: %prog view [options] file_name")
|
||||
elif action == "encrypt":
|
||||
parser.set_usage("usage: %prog encrypt [options] file_name")
|
||||
elif action == "rekey":
|
||||
parser.set_usage("usage: %prog rekey [options] file_name")
|
||||
|
||||
# done, return the parser
|
||||
return parser
|
||||
|
||||
def get_action(args):
|
||||
"""
|
||||
Get the action the user wants to execute from the
|
||||
sys argv list.
|
||||
"""
|
||||
for i in range(0,len(args)):
|
||||
arg = args[i]
|
||||
if arg in VALID_ACTIONS:
|
||||
del args[i]
|
||||
return arg
|
||||
return None
|
||||
|
||||
def get_opt(options, k, defval=""):
|
||||
"""
|
||||
Returns an option from an Optparse values instance.
|
||||
"""
|
||||
try:
|
||||
data = getattr(options, k)
|
||||
except:
|
||||
return defval
|
||||
if k == "roles_path":
|
||||
if os.pathsep in data:
|
||||
data = data.split(os.pathsep)[0]
|
||||
return data
|
||||
|
||||
#-------------------------------------------------------------------------------------
|
||||
# Command functions
|
||||
#-------------------------------------------------------------------------------------
|
||||
|
||||
def execute_create(args, options, parser):
|
||||
if len(args) > 1:
|
||||
raise errors.AnsibleError("'create' does not accept more than one filename")
|
||||
|
||||
if not options.password_file:
|
||||
password, new_password = utils.ask_vault_passwords(ask_vault_pass=True, confirm_vault=True)
|
||||
else:
|
||||
password = utils.read_vault_file(options.password_file)
|
||||
|
||||
cipher = 'AES256'
|
||||
if hasattr(options, 'cipher'):
|
||||
cipher = options.cipher
|
||||
|
||||
this_editor = VaultEditor(cipher, password, args[0])
|
||||
this_editor.create_file()
|
||||
|
||||
def execute_decrypt(args, options, parser):
|
||||
|
||||
if not options.password_file:
|
||||
password, new_password = utils.ask_vault_passwords(ask_vault_pass=True)
|
||||
else:
|
||||
password = utils.read_vault_file(options.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("edit does not accept more than one filename")
|
||||
|
||||
if not options.password_file:
|
||||
password, new_password = utils.ask_vault_passwords(ask_vault_pass=True)
|
||||
else:
|
||||
password = utils.read_vault_file(options.password_file)
|
||||
|
||||
cipher = None
|
||||
|
||||
for f in args:
|
||||
this_editor = VaultEditor(cipher, password, f)
|
||||
this_editor.edit_file()
|
||||
|
||||
def execute_view(args, options, parser):
|
||||
|
||||
if len(args) > 1:
|
||||
raise errors.AnsibleError("view does not accept more than one filename")
|
||||
|
||||
if not options.password_file:
|
||||
password, new_password = utils.ask_vault_passwords(ask_vault_pass=True)
|
||||
else:
|
||||
password = utils.read_vault_file(options.password_file)
|
||||
|
||||
cipher = None
|
||||
|
||||
for f in args:
|
||||
this_editor = VaultEditor(cipher, password, f)
|
||||
this_editor.view_file()
|
||||
|
||||
def execute_encrypt(args, options, parser):
|
||||
|
||||
if not options.password_file:
|
||||
password, new_password = utils.ask_vault_passwords(ask_vault_pass=True, confirm_vault=True)
|
||||
else:
|
||||
password = utils.read_vault_file(options.password_file)
|
||||
|
||||
cipher = 'AES256'
|
||||
if hasattr(options, 'cipher'):
|
||||
cipher = options.cipher
|
||||
|
||||
for f in args:
|
||||
this_editor = VaultEditor(cipher, password, f)
|
||||
this_editor.encrypt_file()
|
||||
|
||||
print "Encryption successful"
|
||||
|
||||
def execute_rekey(args, options, parser):
|
||||
|
||||
if not options.password_file:
|
||||
password, __ = utils.ask_vault_passwords(ask_vault_pass=True)
|
||||
else:
|
||||
password = utils.read_vault_file(options.password_file)
|
||||
|
||||
__, new_password = utils.ask_vault_passwords(ask_vault_pass=False, ask_new_vault_pass=True, confirm_new=True)
|
||||
|
||||
cipher = None
|
||||
for f in args:
|
||||
this_editor = VaultEditor(cipher, password, f)
|
||||
this_editor.rekey_file(new_password)
|
||||
|
||||
print "Rekey successful"
|
||||
|
||||
#-------------------------------------------------------------------------------------
|
||||
# MAIN
|
||||
#-------------------------------------------------------------------------------------
|
||||
|
||||
def main():
|
||||
|
||||
action = get_action(sys.argv)
|
||||
parser = build_option_parser(action)
|
||||
(options, args) = parser.parse_args()
|
||||
|
||||
if not len(args):
|
||||
raise errors.AnsibleError(
|
||||
"The '%s' command requires a filename as the first argument" % action
|
||||
)
|
||||
|
||||
# execute the desired action
|
||||
try:
|
||||
fn = globals()["execute_%s" % action]
|
||||
fn(args, options, parser)
|
||||
except Exception, err:
|
||||
if options.debug:
|
||||
print traceback.format_exc()
|
||||
print "ERROR:",err
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
1
bin/ansible-vault
Symbolic link
1
bin/ansible-vault
Symbolic link
|
@ -0,0 +1 @@
|
|||
ansible
|
17
contrib/README.md
Normal file
17
contrib/README.md
Normal file
|
@ -0,0 +1,17 @@
|
|||
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!
|
||||
|
|
@ -222,12 +222,17 @@ class LibcloudInventory(object):
|
|||
self.push(self.inventory, self.to_safe('type_' + node.instance_type), dest)
|
||||
'''
|
||||
# Inventory: Group by key pair
|
||||
if node.extra['keyname']:
|
||||
self.push(self.inventory, self.to_safe('key_' + node.extra['keyname']), dest)
|
||||
if node.extra['key_name']:
|
||||
self.push(self.inventory, self.to_safe('key_' + node.extra['key_name']), dest)
|
||||
|
||||
# Inventory: Group by security group, quick thing to handle single sg
|
||||
if node.extra['securitygroup']:
|
||||
self.push(self.inventory, self.to_safe('sg_' + node.extra['securitygroup'][0]), dest)
|
||||
if node.extra['security_group']:
|
||||
self.push(self.inventory, self.to_safe('sg_' + node.extra['security_group'][0]), dest)
|
||||
|
||||
# Inventory: Group by tag
|
||||
if node.extra['tags']:
|
||||
for tagkey in node.extra['tags'].keys():
|
||||
self.push(self.inventory, self.to_safe('tag_' + tagkey + '_' + node.extra['tags'][tagkey]), dest)
|
||||
|
||||
def get_host_info(self):
|
||||
'''
|
5
contrib/inventory/cloudstack.ini
Normal file
5
contrib/inventory/cloudstack.ini
Normal file
|
@ -0,0 +1,5 @@
|
|||
[cloudstack]
|
||||
#endpoint = https://api.exoscale.ch/compute
|
||||
endpoint = https://cloud.example.com/client/api
|
||||
key = cloudstack api key
|
||||
secret = cloudstack api secret
|
232
contrib/inventory/cloudstack.py
Executable file
232
contrib/inventory/cloudstack.py
Executable file
|
@ -0,0 +1,232 @@
|
|||
#!/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]
|
||||
"""
|
||||
|
||||
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 >> sys.stderr, "Error: CloudStack library must be installed: pip install cs."
|
||||
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, e:
|
||||
print >> sys.stderr, "Error: Could not connect to CloudStack API"
|
||||
|
||||
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 >> sys.stderr, "usage: --list | --host <hostname> [--project <project>]"
|
||||
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 >> sys.stderr, "Error: Project %s not found." % project
|
||||
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,4 +1,4 @@
|
|||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Cobbler external inventory script
|
|
@ -41,7 +41,9 @@ that will be used instead of the configured values if they are set:
|
|||
If errors are encountered during operation, this script will return an exit code of
|
||||
255; otherwise, it will return an exit code of 0.
|
||||
|
||||
Tested against Ansible 1.6.6 and Collins 1.2.4.
|
||||
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>
|
||||
|
@ -305,6 +307,8 @@ class CollinsInventory(object):
|
|||
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'):
|
||||
|
@ -332,8 +336,8 @@ class CollinsInventory(object):
|
|||
if 'ATTRIBS' in asset:
|
||||
for attrib_block in asset['ATTRIBS'].keys():
|
||||
for attrib in asset['ATTRIBS'][attrib_block].keys():
|
||||
attrib_key = self.to_safe(
|
||||
'%s-%s' % (attrib, asset['ATTRIBS'][attrib_block][attrib]))
|
||||
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.
|
37
contrib/inventory/consul.ini
Normal file
37
contrib/inventory/consul.ini
Normal file
|
@ -0,0 +1,37 @@
|
|||
# Ansible Consul external inventory script settings.
|
||||
|
||||
[consul]
|
||||
|
||||
# restrict included nodes to those from this datacenter
|
||||
#datacenter = nyc1
|
||||
|
||||
# url of the the consul cluster to query
|
||||
#url = http://demo.consul.io
|
||||
url = http://localhost:8500
|
||||
|
||||
# suffix added to each service to create a group name e.g Service of 'redis' and
|
||||
# a suffix of '_servers' will add each address to the group name 'redis_servers'
|
||||
servers_suffix = _servers
|
||||
|
||||
# if specified then the inventory will generate domain names that will resolve
|
||||
# via Consul's inbuilt DNS.
|
||||
#domain=consul
|
||||
|
||||
# make groups from service tags. the name of the group is derived from the
|
||||
# service name and the tag name e.g. a service named nginx with tags ['master', 'v1']
|
||||
# will create groups nginx_master and nginx_v1
|
||||
tags = true
|
||||
|
||||
# looks up the node name at the given path for a list of groups to which the
|
||||
# node should be added.
|
||||
kv_groups=ansible/groups
|
||||
|
||||
# looks up the node name at the given path for a json dictionary of metadata that
|
||||
# should be attached as metadata for the node
|
||||
kv_metadata=ansible/metadata
|
||||
|
||||
# looks up the health of each service and adds the node to 'up' and 'down' groups
|
||||
# based on the service availibility
|
||||
availability = true
|
||||
available_suffix = _up
|
||||
unavailable_suffix = _down
|
428
contrib/inventory/consul_io.py
Executable file
428
contrib/inventory/consul_io.py
Executable file
|
@ -0,0 +1,428 @@
|
|||
#!/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, e:
|
||||
print """failed=True msg='python-consul required for this module. see
|
||||
http://python-consul.readthedocs.org/en/latest/#installation'"""
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
||||
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 node['Services'].iteritems():
|
||||
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()
|
|
@ -3,12 +3,11 @@
|
|||
|
||||
[digital_ocean]
|
||||
|
||||
# The module needs your DigitalOcean Client ID and API Key.
|
||||
# These may also be specified on the command line via --client-id and --api-key
|
||||
# or via the environment variables DO_CLIENT_ID and DO_API_KEY
|
||||
# The module needs your DigitalOcean API Token.
|
||||
# It may also be specified on the command line via --api-token
|
||||
# or via the environment variables DO_API_TOKEN or DO_API_KEY
|
||||
#
|
||||
#client_id = abcdefg123456
|
||||
#api_key = 123456abcdefg
|
||||
#api_token = 123456abcdefg
|
||||
|
||||
|
||||
# API calls to DigitalOcean may be slow. For this reason, we cache the results
|
|
@ -24,12 +24,12 @@ found. You can force this script to use the cache with --force-cache.
|
|||
Configuration is read from `digital_ocean.ini`, then from environment variables,
|
||||
then and command-line arguments.
|
||||
|
||||
Most notably, the DigitalOcean Client ID and API Key must be specified. They
|
||||
can be specified in the INI file or with the following environment variables:
|
||||
export DO_CLIENT_ID='DO123' DO_API_KEY='abc123'
|
||||
Most notably, the DigitalOcean API Token must be specified. It can be specified
|
||||
in the INI file or with the following environment variables:
|
||||
export DO_API_TOKEN='abc123' or
|
||||
export DO_API_KEY='abc123'
|
||||
|
||||
Alternatively, they can be passed on the command-line with --client-id and
|
||||
--api-key.
|
||||
Alternatively, it can be passed on the command-line with --api-token.
|
||||
|
||||
If you specify DigitalOcean credentials in the INI file, a handy way to
|
||||
get them into your environment (e.g., to use the digital_ocean module)
|
||||
|
@ -43,25 +43,30 @@ The following groups are generated from --list:
|
|||
- image_ID
|
||||
- image_NAME
|
||||
- distro_NAME (distribution NAME from image)
|
||||
- region_ID
|
||||
- region_NAME
|
||||
- size_ID
|
||||
- size_NAME
|
||||
- status_STATUS
|
||||
|
||||
When run against a specific host, this script returns the following variables:
|
||||
- do_backup_ids
|
||||
- do_created_at
|
||||
- do_distroy
|
||||
- do_disk
|
||||
- do_features - list
|
||||
- do_id
|
||||
- do_image
|
||||
- do_image_id
|
||||
- do_image - object
|
||||
- do_ip_address
|
||||
- do_kernel - object
|
||||
- do_locked
|
||||
- de_memory
|
||||
- do_name
|
||||
- do_region
|
||||
- do_region_id
|
||||
- do_size
|
||||
- do_size_id
|
||||
- do_networks - object
|
||||
- do_next_backup_window
|
||||
- do_region - object
|
||||
- do_size - object
|
||||
- do_size_slug
|
||||
- do_snapshot_ids - list
|
||||
- do_status
|
||||
- do_vcpus
|
||||
|
||||
-----
|
||||
```
|
||||
|
@ -70,8 +75,9 @@ usage: digital_ocean.py [-h] [--list] [--host HOST] [--all]
|
|||
[--ssh-keys] [--domains] [--pretty]
|
||||
[--cache-path CACHE_PATH]
|
||||
[--cache-max_age CACHE_MAX_AGE]
|
||||
[--refresh-cache] [--client-id CLIENT_ID]
|
||||
[--api-key API_KEY]
|
||||
[--force-cache]
|
||||
[--refresh-cache]
|
||||
[--api-token API_TOKEN]
|
||||
|
||||
Produce an Ansible Inventory file based on DigitalOcean credentials
|
||||
|
||||
|
@ -93,12 +99,11 @@ optional arguments:
|
|||
Path to the cache files (default: .)
|
||||
--cache-max_age CACHE_MAX_AGE
|
||||
Maximum age of the cached items (default: 0)
|
||||
--force-cache Only use data from the cache
|
||||
--refresh-cache Force refresh of cache by making API requests to
|
||||
DigitalOcean (default: False - use cache files)
|
||||
--client-id CLIENT_ID, -c CLIENT_ID
|
||||
DigitalOcean Client ID
|
||||
--api-key API_KEY, -a API_KEY
|
||||
DigitalOcean API Key
|
||||
--api-token API_TOKEN, -a API_TOKEN
|
||||
DigitalOcean API Token
|
||||
```
|
||||
|
||||
'''
|
||||
|
@ -106,7 +111,7 @@ optional arguments:
|
|||
# (c) 2013, Evan Wies <evan@neomantra.net>
|
||||
#
|
||||
# Inspired by the EC2 inventory plugin:
|
||||
# https://github.com/ansible/ansible/blob/devel/plugins/inventory/ec2.py
|
||||
# https://github.com/ansible/ansible/blob/devel/contrib/inventory/ec2.py
|
||||
#
|
||||
# This file is part of Ansible,
|
||||
#
|
||||
|
@ -157,7 +162,6 @@ class DigitalOceanInventory(object):
|
|||
# DigitalOceanInventory data
|
||||
self.data = {} # All DigitalOcean data
|
||||
self.inventory = {} # Ansible Inventory
|
||||
self.index = {} # Various indices of Droplet metadata
|
||||
|
||||
# Define defaults
|
||||
self.cache_path = '.'
|
||||
|
@ -169,49 +173,61 @@ class DigitalOceanInventory(object):
|
|||
self.read_cli_args()
|
||||
|
||||
# Verify credentials were set
|
||||
if not hasattr(self, 'client_id') or not hasattr(self, 'api_key'):
|
||||
print '''Could not find values for DigitalOcean client_id and api_key.
|
||||
They must be specified via either ini file, command line argument (--client-id and --api-key),
|
||||
or environment variables (DO_CLIENT_ID and DO_API_KEY)'''
|
||||
if not hasattr(self, 'api_token'):
|
||||
print '''Could not find values for DigitalOcean api_token.
|
||||
They must be specified via either ini file, command line argument (--api-token),
|
||||
or environment variables (DO_API_TOKEN)'''
|
||||
sys.exit(-1)
|
||||
|
||||
# env command, show DigitalOcean credentials
|
||||
if self.args.env:
|
||||
print "DO_CLIENT_ID=%s DO_API_KEY=%s" % (self.client_id, self.api_key)
|
||||
print "DO_API_TOKEN=%s" % self.api_token
|
||||
sys.exit(0)
|
||||
|
||||
# Manage cache
|
||||
self.cache_filename = self.cache_path + "/ansible-digital_ocean.cache"
|
||||
self.cache_refreshed = False
|
||||
|
||||
if not self.args.force_cache and self.args.refresh_cache or not self.is_cache_valid():
|
||||
self.load_all_data_from_digital_ocean()
|
||||
else:
|
||||
if self.is_cache_valid:
|
||||
self.load_from_cache()
|
||||
if len(self.data) == 0:
|
||||
if self.args.force_cache:
|
||||
print '''Cache is empty and --force-cache was specified'''
|
||||
sys.exit(-1)
|
||||
self.load_all_data_from_digital_ocean()
|
||||
else:
|
||||
# We always get fresh droplets for --list, --host, --all, and --droplets
|
||||
# unless --force-cache is specified
|
||||
if not self.args.force_cache and (
|
||||
self.args.list or self.args.host or self.args.all or self.args.droplets):
|
||||
self.load_droplets_from_digital_ocean()
|
||||
|
||||
self.manager = DoManager(None, self.api_token, api_version=2)
|
||||
|
||||
# Pick the json_data to print based on the CLI command
|
||||
if self.args.droplets: json_data = { 'droplets': self.data['droplets'] }
|
||||
elif self.args.regions: json_data = { 'regions': self.data['regions'] }
|
||||
elif self.args.images: json_data = { 'images': self.data['images'] }
|
||||
elif self.args.sizes: json_data = { 'sizes': self.data['sizes'] }
|
||||
elif self.args.ssh_keys: json_data = { 'ssh_keys': self.data['ssh_keys'] }
|
||||
elif self.args.domains: json_data = { 'domains': self.data['domains'] }
|
||||
elif self.args.all: json_data = self.data
|
||||
|
||||
elif self.args.host: json_data = self.load_droplet_variables_for_host()
|
||||
if self.args.droplets:
|
||||
self.load_from_digital_ocean('droplets')
|
||||
json_data = {'droplets': self.data['droplets']}
|
||||
elif self.args.regions:
|
||||
self.load_from_digital_ocean('regions')
|
||||
json_data = {'regions': self.data['regions']}
|
||||
elif self.args.images:
|
||||
self.load_from_digital_ocean('images')
|
||||
json_data = {'images': self.data['images']}
|
||||
elif self.args.sizes:
|
||||
self.load_from_digital_ocean('sizes')
|
||||
json_data = {'sizes': self.data['sizes']}
|
||||
elif self.args.ssh_keys:
|
||||
self.load_from_digital_ocean('ssh_keys')
|
||||
json_data = {'ssh_keys': self.data['ssh_keys']}
|
||||
elif self.args.domains:
|
||||
self.load_from_digital_ocean('domains')
|
||||
json_data = {'domains': self.data['domains']}
|
||||
elif self.args.all:
|
||||
self.load_from_digital_ocean()
|
||||
json_data = self.data
|
||||
elif self.args.host:
|
||||
json_data = self.load_droplet_variables_for_host()
|
||||
else: # '--list' this is last to make it default
|
||||
json_data = self.inventory
|
||||
self.load_from_digital_ocean('droplets')
|
||||
self.build_inventory()
|
||||
json_data = self.inventory
|
||||
|
||||
if self.cache_refreshed:
|
||||
self.write_to_cache()
|
||||
|
||||
if self.args.pretty:
|
||||
print json.dumps(json_data, sort_keys=True, indent=2)
|
||||
|
@ -230,10 +246,8 @@ or environment variables (DO_CLIENT_ID and DO_API_KEY)'''
|
|||
config.read(os.path.dirname(os.path.realpath(__file__)) + '/digital_ocean.ini')
|
||||
|
||||
# Credentials
|
||||
if config.has_option('digital_ocean', 'client_id'):
|
||||
self.client_id = config.get('digital_ocean', 'client_id')
|
||||
if config.has_option('digital_ocean', 'api_key'):
|
||||
self.api_key = config.get('digital_ocean', 'api_key')
|
||||
if config.has_option('digital_ocean', 'api_token'):
|
||||
self.api_token = config.get('digital_ocean', 'api_token')
|
||||
|
||||
# Cache related
|
||||
if config.has_option('digital_ocean', 'cache_path'):
|
||||
|
@ -245,8 +259,10 @@ or environment variables (DO_CLIENT_ID and DO_API_KEY)'''
|
|||
def read_environment(self):
|
||||
''' Reads the settings from environment variables '''
|
||||
# Setup credentials
|
||||
if os.getenv("DO_CLIENT_ID"): self.client_id = os.getenv("DO_CLIENT_ID")
|
||||
if os.getenv("DO_API_KEY"): self.api_key = os.getenv("DO_API_KEY")
|
||||
if os.getenv("DO_API_TOKEN"):
|
||||
self.api_token = os.getenv("DO_API_TOKEN")
|
||||
if os.getenv("DO_API_KEY"):
|
||||
self.api_token = os.getenv("DO_API_KEY")
|
||||
|
||||
|
||||
def read_cli_args(self):
|
||||
|
@ -269,70 +285,57 @@ or environment variables (DO_CLIENT_ID and DO_API_KEY)'''
|
|||
parser.add_argument('--cache-path', action='store', help='Path to the cache files (default: .)')
|
||||
parser.add_argument('--cache-max_age', action='store', help='Maximum age of the cached items (default: 0)')
|
||||
parser.add_argument('--force-cache', action='store_true', default=False, help='Only use data from the cache')
|
||||
parser.add_argument('--refresh-cache','-r', action='store_true', default=False, help='Force refresh of cache by making API requests to DigitalOcean (default: False - use cache files)')
|
||||
parser.add_argument('--refresh-cache','-r', action='store_true', default=False,
|
||||
help='Force refresh of cache by making API requests to DigitalOcean (default: False - use cache files)')
|
||||
|
||||
parser.add_argument('--env','-e', action='store_true', help='Display DO_CLIENT_ID and DO_API_KEY')
|
||||
parser.add_argument('--client-id','-c', action='store', help='DigitalOcean Client ID')
|
||||
parser.add_argument('--api-key','-a', action='store', help='DigitalOcean API Key')
|
||||
parser.add_argument('--env','-e', action='store_true', help='Display DO_API_TOKEN')
|
||||
parser.add_argument('--api-token','-a', action='store', help='DigitalOcean API Token')
|
||||
|
||||
self.args = parser.parse_args()
|
||||
|
||||
if self.args.client_id: self.client_id = self.args.client_id
|
||||
if self.args.api_key: self.api_key = self.args.api_key
|
||||
if self.args.cache_path: self.cache_path = self.args.cache_path
|
||||
if self.args.cache_max_age: self.cache_max_age = self.args.cache_max_age
|
||||
if self.args.api_token:
|
||||
self.api_token = self.args.api_token
|
||||
|
||||
# Make --list default if none of the other commands are specified
|
||||
if (not self.args.droplets and not self.args.regions and not self.args.images and
|
||||
not self.args.sizes and not self.args.ssh_keys and not self.args.domains and
|
||||
not self.args.all and not self.args.host):
|
||||
self.args.list = True
|
||||
if (not self.args.droplets and not self.args.regions and
|
||||
not self.args.images and not self.args.sizes and
|
||||
not self.args.ssh_keys and not self.args.domains and
|
||||
not self.args.all and not self.args.host):
|
||||
self.args.list = True
|
||||
|
||||
|
||||
###########################################################################
|
||||
# Data Management
|
||||
###########################################################################
|
||||
|
||||
def load_all_data_from_digital_ocean(self):
|
||||
''' Use dopy to get all the information from DigitalOcean and save data in cache files '''
|
||||
manager = DoManager(self.client_id, self.api_key)
|
||||
def load_from_digital_ocean(self, resource=None):
|
||||
'''Get JSON from DigitalOcean API'''
|
||||
if self.args.force_cache:
|
||||
return
|
||||
# We always get fresh droplets
|
||||
if self.is_cache_valid() and not (resource=='droplets' or resource is None):
|
||||
return
|
||||
if self.args.refresh_cache:
|
||||
resource=None
|
||||
|
||||
self.data = {}
|
||||
self.data['droplets'] = self.sanitize_list(manager.all_active_droplets())
|
||||
self.data['regions'] = self.sanitize_list(manager.all_regions())
|
||||
self.data['images'] = self.sanitize_list(manager.all_images(filter=None))
|
||||
self.data['sizes'] = self.sanitize_list(manager.sizes())
|
||||
self.data['ssh_keys'] = self.sanitize_list(manager.all_ssh_keys())
|
||||
self.data['domains'] = self.sanitize_list(manager.all_domains())
|
||||
|
||||
self.index = {}
|
||||
self.index['region_to_name'] = self.build_index(self.data['regions'], 'id', 'name')
|
||||
self.index['size_to_name'] = self.build_index(self.data['sizes'], 'id', 'name')
|
||||
self.index['image_to_name'] = self.build_index(self.data['images'], 'id', 'name')
|
||||
self.index['image_to_distro'] = self.build_index(self.data['images'], 'id', 'distribution')
|
||||
self.index['host_to_droplet'] = self.build_index(self.data['droplets'], 'ip_address', 'id', False)
|
||||
|
||||
self.build_inventory()
|
||||
|
||||
self.write_to_cache()
|
||||
|
||||
|
||||
def load_droplets_from_digital_ocean(self):
|
||||
''' Use dopy to get droplet information from DigitalOcean and save data in cache files '''
|
||||
manager = DoManager(self.client_id, self.api_key)
|
||||
self.data['droplets'] = self.sanitize_list(manager.all_active_droplets())
|
||||
self.index['host_to_droplet'] = self.build_index(self.data['droplets'], 'ip_address', 'id', False)
|
||||
self.build_inventory()
|
||||
self.write_to_cache()
|
||||
|
||||
|
||||
def build_index(self, source_seq, key_from, key_to, use_slug=True):
|
||||
dest_dict = {}
|
||||
for item in source_seq:
|
||||
name = (use_slug and item.has_key('slug')) and item['slug'] or item[key_to]
|
||||
key = item[key_from]
|
||||
dest_dict[key] = name
|
||||
return dest_dict
|
||||
if resource == 'droplets' or resource is None:
|
||||
self.data['droplets'] = self.manager.all_active_droplets()
|
||||
self.cache_refreshed = True
|
||||
if resource == 'regions' or resource is None:
|
||||
self.data['regions'] = self.manager.all_regions()
|
||||
self.cache_refreshed = True
|
||||
if resource == 'images' or resource is None:
|
||||
self.data['images'] = self.manager.all_images(filter=None)
|
||||
self.cache_refreshed = True
|
||||
if resource == 'sizes' or resource is None:
|
||||
self.data['sizes'] = self.manager.sizes()
|
||||
self.cache_refreshed = True
|
||||
if resource == 'ssh_keys' or resource is None:
|
||||
self.data['ssh_keys'] = self.manager.all_ssh_keys()
|
||||
self.cache_refreshed = True
|
||||
if resource == 'domains' or resource is None:
|
||||
self.data['domains'] = self.manager.all_domains()
|
||||
self.cache_refreshed = True
|
||||
|
||||
|
||||
def build_inventory(self):
|
||||
|
@ -345,70 +348,34 @@ or environment variables (DO_CLIENT_ID and DO_API_KEY)'''
|
|||
|
||||
self.inventory[droplet['id']] = [dest]
|
||||
self.push(self.inventory, droplet['name'], dest)
|
||||
self.push(self.inventory, 'region_'+droplet['region_id'], dest)
|
||||
self.push(self.inventory, 'image_' +droplet['image_id'], dest)
|
||||
self.push(self.inventory, 'size_' +droplet['size_id'], dest)
|
||||
self.push(self.inventory, 'status_'+droplet['status'], dest)
|
||||
self.push(self.inventory, 'region_' + droplet['region']['slug'], dest)
|
||||
self.push(self.inventory, 'image_' + str(droplet['image']['id']), dest)
|
||||
self.push(self.inventory, 'size_' + droplet['size']['slug'], dest)
|
||||
|
||||
region_name = self.index['region_to_name'].get(droplet['region_id'])
|
||||
if region_name:
|
||||
self.push(self.inventory, 'region_'+region_name, dest)
|
||||
image_slug = droplet['image']['slug']
|
||||
if image_slug:
|
||||
self.push(self.inventory, 'image_' + self.to_safe(image_slug), dest)
|
||||
else:
|
||||
image_name = droplet['image']['name']
|
||||
if image_name:
|
||||
self.push(self.inventory, 'image_' + self.to_safe(image_name), dest)
|
||||
|
||||
size_name = self.index['size_to_name'].get(droplet['size_id'])
|
||||
if size_name:
|
||||
self.push(self.inventory, 'size_'+size_name, dest)
|
||||
|
||||
image_name = self.index['image_to_name'].get(droplet['image_id'])
|
||||
if image_name:
|
||||
self.push(self.inventory, 'image_'+image_name, dest)
|
||||
|
||||
distro_name = self.index['image_to_distro'].get(droplet['image_id'])
|
||||
if distro_name:
|
||||
self.push(self.inventory, 'distro_'+distro_name, dest)
|
||||
self.push(self.inventory, 'distro_' + self.to_safe(droplet['image']['distribution']), dest)
|
||||
self.push(self.inventory, 'status_' + droplet['status'], dest)
|
||||
|
||||
|
||||
def load_droplet_variables_for_host(self):
|
||||
'''Generate a JSON response to a --host call'''
|
||||
host = self.to_safe(str(self.args.host))
|
||||
host = int(self.args.host)
|
||||
|
||||
if not host in self.index['host_to_droplet']:
|
||||
# try updating cache
|
||||
if not self.args.force_cache:
|
||||
self.load_all_data_from_digital_ocean()
|
||||
if not host in self.index['host_to_droplet']:
|
||||
# host might not exist anymore
|
||||
return {}
|
||||
|
||||
droplet = None
|
||||
if self.cache_refreshed:
|
||||
for drop in self.data['droplets']:
|
||||
if drop['ip_address'] == host:
|
||||
droplet = self.sanitize_dict(drop)
|
||||
break
|
||||
else:
|
||||
# Cache wasn't refreshed this run, so hit DigitalOcean API
|
||||
manager = DoManager(self.client_id, self.api_key)
|
||||
droplet_id = self.index['host_to_droplet'][host]
|
||||
droplet = self.sanitize_dict(manager.show_droplet(droplet_id))
|
||||
|
||||
if not droplet:
|
||||
return {}
|
||||
droplet = self.manager.show_droplet(host)
|
||||
|
||||
# Put all the information in a 'do_' namespace
|
||||
info = {}
|
||||
for k, v in droplet.items():
|
||||
info['do_'+k] = v
|
||||
|
||||
# Generate user-friendly variables (i.e. not the ID's)
|
||||
if droplet.has_key('region_id'):
|
||||
info['do_region'] = self.index['region_to_name'].get(droplet['region_id'])
|
||||
if droplet.has_key('size_id'):
|
||||
info['do_size'] = self.index['size_to_name'].get(droplet['size_id'])
|
||||
if droplet.has_key('image_id'):
|
||||
info['do_image'] = self.index['image_to_name'].get(droplet['image_id'])
|
||||
info['do_distro'] = self.index['image_to_distro'].get(droplet['image_id'])
|
||||
|
||||
return info
|
||||
return {'droplet': info}
|
||||
|
||||
|
||||
|
||||
|
@ -428,19 +395,21 @@ or environment variables (DO_CLIENT_ID and DO_API_KEY)'''
|
|||
|
||||
def load_from_cache(self):
|
||||
''' Reads the data from the cache file and assigns it to member variables as Python Objects'''
|
||||
cache = open(self.cache_filename, 'r')
|
||||
json_data = cache.read()
|
||||
cache.close()
|
||||
data = json.loads(json_data)
|
||||
try:
|
||||
cache = open(self.cache_filename, 'r')
|
||||
json_data = cache.read()
|
||||
cache.close()
|
||||
data = json.loads(json_data)
|
||||
except IOError:
|
||||
data = {'data': {}, 'inventory': {}}
|
||||
|
||||
self.data = data['data']
|
||||
self.inventory = data['inventory']
|
||||
self.index = data['index']
|
||||
|
||||
|
||||
def write_to_cache(self):
|
||||
''' Writes data in JSON format to a file '''
|
||||
data = { 'data': self.data, 'index': self.index, 'inventory': self.inventory }
|
||||
data = { 'data': self.data, 'inventory': self.inventory }
|
||||
json_data = json.dumps(data, sort_keys=True, indent=2)
|
||||
|
||||
cache = open(self.cache_filename, 'w')
|
||||
|
@ -448,7 +417,6 @@ or environment variables (DO_CLIENT_ID and DO_API_KEY)'''
|
|||
cache.close()
|
||||
|
||||
|
||||
|
||||
###########################################################################
|
||||
# Utilities
|
||||
###########################################################################
|
||||
|
@ -456,7 +424,7 @@ or environment variables (DO_CLIENT_ID and DO_API_KEY)'''
|
|||
def push(self, my_dict, key, element):
|
||||
''' Pushed an element onto an array that may not have been defined in the dict '''
|
||||
if key in my_dict:
|
||||
my_dict[key].append(element);
|
||||
my_dict[key].append(element)
|
||||
else:
|
||||
my_dict[key] = [element]
|
||||
|
||||
|
@ -466,21 +434,6 @@ or environment variables (DO_CLIENT_ID and DO_API_KEY)'''
|
|||
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
|
||||
|
||||
|
||||
|
||||
###########################################################################
|
||||
# Run the script
|
|
@ -24,14 +24,20 @@ regions_exclude = us-gov-west-1,cn-north-1
|
|||
# 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'.
|
||||
# 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 with EC2.
|
||||
# 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
|
||||
|
@ -41,6 +47,9 @@ 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
|
||||
|
@ -53,6 +62,18 @@ all_instances = False
|
|||
# '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:
|
||||
|
@ -68,11 +89,31 @@ cache_max_age = 300
|
|||
# Organize groups into a nested/hierarchy instead of a flat namespace.
|
||||
nested_groups = 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 = stage-*
|
||||
# pattern_include = staging-*
|
||||
|
||||
# If you want to exclude any hosts that match a certain regular expression
|
||||
# pattern_exclude = stage-*
|
||||
# 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
|
||||
|
@ -80,14 +121,14 @@ nested_groups = False
|
|||
# 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=stage tag
|
||||
# instance_filters = tag:env=stage
|
||||
# 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=stage
|
||||
# instance_filters = instance-type=t1.micro,tag:env=stage
|
||||
# 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*
|
1237
contrib/inventory/ec2.py
Executable file
1237
contrib/inventory/ec2.py
Executable file
File diff suppressed because it is too large
Load diff
107
contrib/inventory/fleet.py
Executable file
107
contrib/inventory/fleet.py
Executable file
|
@ -0,0 +1,107 @@
|
|||
#!/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)
|
83
contrib/inventory/freeipa.py
Executable file
83
contrib/inventory/freeipa.py
Executable file
|
@ -0,0 +1,83 @@
|
|||
#!/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,4 +1,4 @@
|
|||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
# Copyright 2013 Google Inc.
|
||||
#
|
||||
# This file is part of Ansible
|
||||
|
@ -66,12 +66,22 @@ Examples:
|
|||
$ ansible -i gce.py us-central1-a -m shell -a "/bin/uname -a"
|
||||
|
||||
Use the GCE inventory script to print out instance specific information
|
||||
$ plugins/inventory/gce.py --host my_instance
|
||||
$ contrib/inventory/gce.py --host my_instance
|
||||
|
||||
Author: Eric Johnson <erjohnso@google.com>
|
||||
Version: 0.0.1
|
||||
'''
|
||||
|
||||
__requires__ = ['pycrypto>=2.6']
|
||||
try:
|
||||
import pkg_resources
|
||||
except ImportError:
|
||||
# Use pkg_resources to find the correct versions of libraries and set
|
||||
# sys.path appropriately when there are multiversion installs. We don't
|
||||
# fail here as there is code that better expresses the errors where the
|
||||
# library is used.
|
||||
pass
|
||||
|
||||
USER_AGENT_PRODUCT="Ansible-gce_inventory_plugin"
|
||||
USER_AGENT_VERSION="v1"
|
||||
|
||||
|
@ -211,7 +221,7 @@ class GceInventory(object):
|
|||
'gce_image': inst.image,
|
||||
'gce_machine_type': inst.size,
|
||||
'gce_private_ip': inst.private_ips[0],
|
||||
'gce_public_ip': inst.public_ips[0],
|
||||
'gce_public_ip': inst.public_ips[0] if len(inst.public_ips) >= 1 else None,
|
||||
'gce_name': inst.name,
|
||||
'gce_description': inst.extra['description'],
|
||||
'gce_status': inst.extra['status'],
|
||||
|
@ -220,7 +230,7 @@ class GceInventory(object):
|
|||
'gce_metadata': md,
|
||||
'gce_network': net,
|
||||
# Hosts don't have a public name, so we add an IP
|
||||
'ansible_ssh_host': inst.public_ips[0]
|
||||
'ansible_ssh_host': inst.public_ips[0] if len(inst.public_ips) >= 1 else inst.private_ips[0]
|
||||
}
|
||||
|
||||
def get_instance(self, instance_name):
|
||||
|
@ -247,7 +257,10 @@ class GceInventory(object):
|
|||
|
||||
tags = node.extra['tags']
|
||||
for t in tags:
|
||||
tag = 'tag_%s' % t
|
||||
if t.startswith('group-'):
|
||||
tag = t[6:]
|
||||
else:
|
||||
tag = 'tag_%s' % t
|
||||
if groups.has_key(tag): groups[tag].append(name)
|
||||
else: groups[tag] = [name]
|
||||
|
128
contrib/inventory/landscape.py
Executable file
128
contrib/inventory/landscape.py
Executable file
|
@ -0,0 +1,128 @@
|
|||
#!/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:])
|
48
contrib/inventory/nova.ini
Normal file
48
contrib/inventory/nova.ini
Normal file
|
@ -0,0 +1,48 @@
|
|||
# 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 =
|
229
contrib/inventory/nova.py
Executable file
229
contrib/inventory/nova.py
Executable file
|
@ -0,0 +1,229 @@
|
|||
#!/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
|
||||
|
||||
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 server.metadata.iteritems():
|
||||
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)
|
161
contrib/inventory/openstack.py
Executable file
161
contrib/inventory/openstack.py
Executable file
|
@ -0,0 +1,161 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# Copyright (c) 2012, Marco Vito Moscaritolo <marco@agavee.com>
|
||||
# Copyright (c) 2013, Jesse Keating <jesse.keating@rackspace.com>
|
||||
# Copyright (c) 2014, 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
|
||||
|
||||
import argparse
|
||||
import collections
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
try:
|
||||
import json
|
||||
except:
|
||||
import simplejson as json
|
||||
|
||||
import os_client_config
|
||||
import shade
|
||||
|
||||
|
||||
class OpenStackInventory(object):
|
||||
|
||||
def __init__(self, private=False, refresh=False):
|
||||
self.openstack_config = os_client_config.config.OpenStackConfig(
|
||||
os_client_config.config.CONFIG_FILES.append(
|
||||
'/etc/ansible/openstack.yml'),
|
||||
private)
|
||||
self.clouds = shade.openstack_clouds(self.openstack_config)
|
||||
self.refresh = refresh
|
||||
|
||||
self.cache_max_age = self.openstack_config.get_cache_max_age()
|
||||
cache_path = self.openstack_config.get_cache_path()
|
||||
|
||||
# Cache related
|
||||
if not os.path.exists(cache_path):
|
||||
os.makedirs(cache_path)
|
||||
self.cache_file = os.path.join(cache_path, "ansible-inventory.cache")
|
||||
|
||||
def is_cache_stale(self):
|
||||
''' Determines if cache file has expired, or if it is still valid '''
|
||||
if os.path.isfile(self.cache_file):
|
||||
mod_time = os.path.getmtime(self.cache_file)
|
||||
current_time = time.time()
|
||||
if (mod_time + self.cache_max_age) > current_time:
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_host_groups(self):
|
||||
if self.refresh or self.is_cache_stale():
|
||||
groups = self.get_host_groups_from_cloud()
|
||||
self.write_cache(groups)
|
||||
else:
|
||||
return json.load(open(self.cache_file, 'r'))
|
||||
return groups
|
||||
|
||||
def write_cache(self, groups):
|
||||
with open(self.cache_file, 'w') as cache_file:
|
||||
cache_file.write(self.json_format_dict(groups))
|
||||
|
||||
def get_host_groups_from_cloud(self):
|
||||
groups = collections.defaultdict(list)
|
||||
hostvars = collections.defaultdict(dict)
|
||||
|
||||
for cloud in self.clouds:
|
||||
|
||||
# Cycle on servers
|
||||
for server in cloud.list_servers():
|
||||
|
||||
meta = cloud.get_server_meta(server)
|
||||
|
||||
if 'interface_ip' not in meta['server_vars']:
|
||||
# skip this host if it doesn't have a network address
|
||||
continue
|
||||
|
||||
server_vars = meta['server_vars']
|
||||
hostvars[server.name][
|
||||
'ansible_ssh_host'] = server_vars['interface_ip']
|
||||
hostvars[server.name]['openstack'] = server_vars
|
||||
|
||||
for group in meta['groups']:
|
||||
groups[group].append(server.name)
|
||||
|
||||
if hostvars:
|
||||
groups['_meta'] = {'hostvars': hostvars}
|
||||
return groups
|
||||
|
||||
def json_format_dict(self, data):
|
||||
return json.dumps(data, sort_keys=True, indent=2)
|
||||
|
||||
def list_instances(self):
|
||||
groups = self.get_host_groups()
|
||||
# Return server list
|
||||
print(self.json_format_dict(groups))
|
||||
|
||||
def get_host(self, hostname):
|
||||
groups = self.get_host_groups()
|
||||
hostvars = groups['_meta']['hostvars']
|
||||
if hostname in hostvars:
|
||||
print(self.json_format_dict(hostvars[hostname]))
|
||||
|
||||
|
||||
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')
|
||||
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:
|
||||
inventory = OpenStackInventory(args.private, args.refresh)
|
||||
if args.list:
|
||||
inventory.list_instances()
|
||||
elif args.host:
|
||||
inventory.get_host(args.host)
|
||||
except shade.OpenStackCloudException as e:
|
||||
print(e.message)
|
||||
sys.exit(1)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
28
contrib/inventory/openstack.yml
Normal file
28
contrib/inventory/openstack.yml
Normal file
|
@ -0,0 +1,28 @@
|
|||
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
|
33
contrib/inventory/ovirt.ini
Normal file
33
contrib/inventory/ovirt.ini
Normal file
|
@ -0,0 +1,33 @@
|
|||
# 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 =
|
287
contrib/inventory/ovirt.py
Executable file
287
contrib/inventory/ovirt.py
Executable file
|
@ -0,0 +1,287 @@
|
|||
#!/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()
|
57
contrib/inventory/rax.ini
Normal file
57
contrib/inventory/rax.ini
Normal file
|
@ -0,0 +1,57 @@
|
|||
# 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
|
420
contrib/inventory/rax.py
Executable file
420
contrib/inventory/rax.py
Executable file
|
@ -0,0 +1,420 @@
|
|||
#!/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 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)
|
||||
|
||||
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(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 a NoneType. 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 server.metadata.iteritems():
|
||||
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}
|
||||
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')
|
||||
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, 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)
|
||||
elif args.host:
|
||||
host(regions, args.host)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
115
contrib/inventory/serf.py
Executable file
115
contrib/inventory/serf.py
Executable file
|
@ -0,0 +1,115 @@
|
|||
#!/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:])
|
|
@ -55,7 +55,7 @@ class SoftLayerInventory(object):
|
|||
self.get_all_servers()
|
||||
print self.json_format_dict(self.inventory, True)
|
||||
elif self.args.host:
|
||||
self.get_virtual_servers(client)
|
||||
self.get_virtual_servers()
|
||||
print self.json_format_dict(self.inventory["_meta"]["hostvars"][self.args.host], True)
|
||||
|
||||
def to_safe(self, word):
|
|
@ -13,6 +13,7 @@ Example Vagrant configuration using this script:
|
|||
"""
|
||||
|
||||
# Copyright (C) 2013 Mark Mandel <mark@compoundtheory.com>
|
||||
# 2015 Igor Khomyakov <homyakov@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
|
||||
|
@ -33,17 +34,26 @@ Example Vagrant configuration using this script:
|
|||
#
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
import subprocess
|
||||
import re
|
||||
import string
|
||||
from paramiko import SSHConfig
|
||||
from cStringIO import StringIO
|
||||
from optparse import OptionParser
|
||||
from collections import defaultdict
|
||||
try:
|
||||
import json
|
||||
except:
|
||||
import simplejson as json
|
||||
|
||||
_group = 'vagrant' # a default group
|
||||
_ssh_to_ansible = [('user', 'ansible_ssh_user'),
|
||||
('hostname', 'ansible_ssh_host'),
|
||||
('identityfile', 'ansible_ssh_private_key_file'),
|
||||
('port', 'ansible_ssh_port')]
|
||||
|
||||
# Options
|
||||
#------------------------------
|
||||
# ------------------------------
|
||||
|
||||
parser = OptionParser(usage="%prog [options] --list | --host <machine>")
|
||||
parser.add_option('--list', default=False, dest="list", action="store_true",
|
||||
|
@ -56,19 +66,13 @@ parser.add_option('--host', default=None, dest="host",
|
|||
# helper functions
|
||||
#
|
||||
|
||||
|
||||
# get all the ssh configs for all boxes in an array of dictionaries.
|
||||
def get_ssh_config():
|
||||
configs = []
|
||||
return {k: get_a_ssh_config(k) for k in list_running_boxes()}
|
||||
|
||||
boxes = list_running_boxes()
|
||||
|
||||
for box in boxes:
|
||||
config = get_a_ssh_config(box)
|
||||
configs.append(config)
|
||||
|
||||
return configs
|
||||
|
||||
#list all the running boxes
|
||||
# list all the running boxes
|
||||
def list_running_boxes():
|
||||
output = subprocess.check_output(["vagrant", "status"]).split('\n')
|
||||
|
||||
|
@ -79,54 +83,47 @@ def list_running_boxes():
|
|||
if matcher:
|
||||
boxes.append(matcher.group(1))
|
||||
|
||||
|
||||
return boxes
|
||||
|
||||
#get the ssh config for a single box
|
||||
|
||||
# get the ssh config for a single box
|
||||
def get_a_ssh_config(box_name):
|
||||
"""Gives back a map of all the machine's ssh configurations"""
|
||||
|
||||
output = subprocess.check_output(["vagrant", "ssh-config", box_name]).split('\n')
|
||||
output = subprocess.check_output(["vagrant", "ssh-config", box_name])
|
||||
config = SSHConfig()
|
||||
config.parse(StringIO(output))
|
||||
host_config = config.lookup(box_name)
|
||||
|
||||
config = {}
|
||||
for line in output:
|
||||
if line.strip() != '':
|
||||
matcher = re.search("( )?([a-zA-Z]+) (.*)", line)
|
||||
config[matcher.group(2)] = matcher.group(3)
|
||||
|
||||
return config
|
||||
# man 5 ssh_config:
|
||||
# > It is possible to have multiple identity files ...
|
||||
# > all these identities will be tried in sequence.
|
||||
for id in host_config['identityfile']:
|
||||
if os.path.isfile(id):
|
||||
host_config['identityfile'] = id
|
||||
|
||||
return {v: host_config[k] for k, v in _ssh_to_ansible}
|
||||
|
||||
# List out servers that vagrant has running
|
||||
#------------------------------
|
||||
# ------------------------------
|
||||
if options.list:
|
||||
ssh_config = get_ssh_config()
|
||||
hosts = { 'vagrant': []}
|
||||
meta = defaultdict(dict)
|
||||
|
||||
for data in ssh_config:
|
||||
hosts['vagrant'].append(data['HostName'])
|
||||
for host in ssh_config:
|
||||
meta['hostvars'][host] = ssh_config[host]
|
||||
|
||||
print json.dumps(hosts)
|
||||
sys.exit(1)
|
||||
print json.dumps({_group: list(ssh_config.keys()), '_meta': meta})
|
||||
sys.exit(0)
|
||||
|
||||
# Get out the host details
|
||||
#------------------------------
|
||||
# ------------------------------
|
||||
elif options.host:
|
||||
result = {}
|
||||
ssh_config = get_ssh_config()
|
||||
|
||||
details = filter(lambda x: (x['HostName'] == options.host), ssh_config)
|
||||
if len(details) > 0:
|
||||
#pass through the port, in case it's non standard.
|
||||
result = details[0]
|
||||
result['ansible_ssh_port'] = result['Port']
|
||||
|
||||
print json.dumps(result)
|
||||
sys.exit(1)
|
||||
|
||||
print json.dumps(get_a_ssh_config(options.host))
|
||||
sys.exit(0)
|
||||
|
||||
# Print out help
|
||||
#------------------------------
|
||||
# ------------------------------
|
||||
else:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
sys.exit(0)
|
114
contrib/inventory/vbox.py
Executable file
114
contrib/inventory/vbox.py
Executable file
|
@ -0,0 +1,114 @@
|
|||
#!/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
|
||||
|
||||
|
||||
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()
|
||||
|
||||
import pprint
|
||||
print pprint.pprint(inventory)
|
43
contrib/inventory/vmware.ini
Normal file
43
contrib/inventory/vmware.ini
Normal file
|
@ -0,0 +1,43 @@
|
|||
# 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
|
432
contrib/inventory/vmware.py
Executable file
432
contrib/inventory/vmware.py
Executable file
|
@ -0,0 +1,432 @@
|
|||
#!/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.
|
||||
'''
|
||||
|
||||
import collections
|
||||
import json
|
||||
import logging
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import ConfigParser
|
||||
|
||||
# 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 = unicode(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, 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, e:
|
||||
print >> sys.stderr, e
|
||||
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()
|
|
@ -20,7 +20,7 @@
|
|||
######################################################################
|
||||
|
||||
"""
|
||||
Zabbix Server external inventory script.
|
||||
Zabbix Server external inventory script.
|
||||
========================================
|
||||
|
||||
Returns hosts and hostgroups from Zabbix Server.
|
||||
|
@ -31,7 +31,6 @@ Tested with Zabbix Server 2.0.6.
|
|||
"""
|
||||
|
||||
import os, sys
|
||||
import json
|
||||
import argparse
|
||||
import ConfigParser
|
||||
|
||||
|
@ -55,7 +54,7 @@ class ZabbixInventory(object):
|
|||
if config.has_option('zabbix', 'server'):
|
||||
self.zabbix_server = config.get('zabbix', 'server')
|
||||
|
||||
# login
|
||||
# login
|
||||
if config.has_option('zabbix', 'username'):
|
||||
self.zabbix_username = config.get('zabbix', 'username')
|
||||
if config.has_option('zabbix', 'password'):
|
||||
|
@ -84,7 +83,7 @@ class ZabbixInventory(object):
|
|||
|
||||
for host in hostsData:
|
||||
hostname = host['name']
|
||||
data[self.defaultgroup]['hosts'].append(hostname)
|
||||
data[self.defaultgroup]['hosts'].append(hostname)
|
||||
|
||||
for group in host['groups']:
|
||||
groupname = group['name']
|
43
contrib/inventory/zone.py
Executable file
43
contrib/inventory/zone.py
Executable file
|
@ -0,0 +1,43 @@
|
|||
#!/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>"
|
|
@ -1,13 +1,13 @@
|
|||
'\" t
|
||||
.\" Title: ansible-doc
|
||||
.\" Author: :doctype:manpage
|
||||
.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
|
||||
.\" Date: 05/26/2014
|
||||
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
|
||||
.\" Date: 12/09/2014
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 1.7
|
||||
.\" Source: Ansible 1.9
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE\-DOC" "1" "05/26/2014" "Ansible 1\&.7" "System administration commands"
|
||||
.TH "ANSIBLE\-DOC" "1" "12/09/2014" "Ansible 1\&.9" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
|
@ -64,9 +64,3 @@ Ansible is released under the terms of the GPLv3 License\&.
|
|||
\fBansible\-playbook\fR(1), \fBansible\fR(1), \fBansible\-pull\fR(1)
|
||||
.sp
|
||||
Extensive documentation is available in the documentation site: http://docs\&.ansible\&.com\&. IRC and mailing list info can be found in file CONTRIBUTING\&.md, available in: https://github\&.com/ansible/ansible
|
||||
.SH "AUTHOR"
|
||||
.PP
|
||||
\fB:doctype:manpage\fR
|
||||
.RS 4
|
||||
Author.
|
||||
.RE
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
'\" t
|
||||
.\" Title: ansible-galaxy
|
||||
.\" Author: [see the "AUTHOR" section]
|
||||
.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
|
||||
.\" Date: 05/26/2014
|
||||
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
|
||||
.\" Date: 05/05/2015
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 1.7
|
||||
.\" Source: Ansible 2.0.0
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE\-GALAXY" "1" "05/26/2014" "Ansible 1\&.7" "System administration commands"
|
||||
.TH "ANSIBLE\-GALAXY" "1" "05/05/2015" "Ansible 2\&.0\&.0" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
|
@ -149,6 +149,11 @@ Force overwriting an existing role\&.
|
|||
.RS 4
|
||||
The path in which the skeleton role will be created\&.The default is the current working directory\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-offline\fR
|
||||
.RS 4
|
||||
Don\(cqt query the galaxy API when creating roles
|
||||
.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\&.
|
||||
|
|
|
@ -122,6 +122,10 @@ Force overwriting an existing role.
|
|||
The path in which the skeleton role will be created.The default is the current
|
||||
working directory.
|
||||
|
||||
*--offline*::
|
||||
|
||||
Don't query the galaxy API when creating roles
|
||||
|
||||
LIST
|
||||
----
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
'\" t
|
||||
.\" Title: ansible-playbook
|
||||
.\" Author: :doctype:manpage
|
||||
.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
|
||||
.\" Date: 05/26/2014
|
||||
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
|
||||
.\" Date: 07/23/2015
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 1.7
|
||||
.\" Source: Ansible %VERSION%
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE\-PLAYBOOK" "1" "05/26/2014" "Ansible 1\&.7" "System administration commands"
|
||||
.TH "ANSIBLE\-PLAYBOOK" "1" "07/23/2015" "Ansible %VERSION%" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
|
@ -43,39 +43,6 @@ 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\&.
|
||||
|
@ -86,52 +53,26 @@ Prompt for the SSH password instead of assuming key\-based authentication with s
|
|||
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
|
||||
\fB\-b\fR, \fB\-\-become\fR
|
||||
.RS 4
|
||||
Desired sudo user (default=root)\&.
|
||||
Run operations with become (nopasswd implied)
|
||||
.RE
|
||||
.PP
|
||||
\fB\-t\fR, \fITAGS\fR, \fB\-\-tags=\fR\fITAGS\fR
|
||||
\fB\-\-become\-method=BECOME_METHOD\fR
|
||||
.RS 4
|
||||
Only run plays and tasks tagged with these values\&.
|
||||
Privilege escalation method to use (default=sudo), valid choices: [ sudo | su | pbrun | pfexec | runas ]
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-skip\-tags=\fR\fISKIP_TAGS\fR
|
||||
\fB\-\-become\-user=BECOME_USER\fR
|
||||
.RS 4
|
||||
Only run plays and tasks whose tags do not match these values\&.
|
||||
Run operations as this user (default=None)\&.
|
||||
.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
|
||||
\fB\-C\fR, \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
|
||||
|
@ -143,15 +84,159 @@ Connection type to use\&. Possible options are
|
|||
is mostly useful for crontab or kickstarts\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-D\fR, \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\-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)\&. To load variables from a file, specify the file preceded by @ (e\&.g\&. @vars\&.yml)\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-flush\-cache\fR
|
||||
.RS 4
|
||||
Clear the fact cache\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-force\-handlers\fR
|
||||
.RS 4
|
||||
Run handlers even if a task fails\&.
|
||||
.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\-h\fR, \fB\-\-help\fR
|
||||
.RS 4
|
||||
Show help page and exit
|
||||
.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\-l\fR \fISUBSET\fR, \fB\-\-limit=\fR\fISUBSET\fR
|
||||
.RS 4
|
||||
Further limits the selected host/group patterns\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-list\-hosts\fR
|
||||
.RS 4
|
||||
Outputs a list of matching hosts; does not execute anything else\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-list\-tags\fR
|
||||
.RS 4
|
||||
List all available tags\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-list\-tasks\fR
|
||||
.RS 4
|
||||
List all tasks that would be executed
|
||||
.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\-\-private\-key=\fR\fIPRIVATE_KEY_FILE\fR
|
||||
.RS 4
|
||||
Use this file to authenticate the connection
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-skip\-tages=\fR\fISKIP_TAGS\fR
|
||||
.RS 4
|
||||
Only run plays and tasks whose tags do not match these values\&.
|
||||
.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\-S\fR, \-\-su*
|
||||
.RS 4
|
||||
Run operations with su (deprecated, use become)
|
||||
.RE
|
||||
.PP
|
||||
\fB\-R SU\-USER\fR, \fB\-\-su\-user=\fR\fISU_USER\fR
|
||||
.RS 4
|
||||
run operations with su as this user (default=root) (deprecated, use become)
|
||||
.RE
|
||||
.PP
|
||||
\fB\-s\fR, \fB\-\-sudo\fR
|
||||
.RS 4
|
||||
Run operations with sudo (nopasswd) (deprecated, use become)
|
||||
.RE
|
||||
.PP
|
||||
\fB\-U\fR, \fISUDO_USER\fR, \fB\-\-sudo\-user=\fR\fISUDO_USER\fR
|
||||
.RS 4
|
||||
Desired sudo user (default=root) (deprecated, use become)\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-skip\-tags=\fR\fISKIP_TAGS\fR
|
||||
.RS 4
|
||||
Only run plays and tasks whose tags do not match these values\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-syntax\-check\fR
|
||||
.RS 4
|
||||
Look for syntax errors in the playbook, but don\(cqt run anything
|
||||
.RE
|
||||
.PP
|
||||
\fB\-t\fR, \fITAGS\fR, \fB\-\-tags=\fR\fITAGS\fR
|
||||
.RS 4
|
||||
Only run plays and tasks tagged with these values\&.
|
||||
.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\-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\-\-vault\-password\-file=\fR\fIVAULT_PASSWORD_FILE\fR
|
||||
.RS 4
|
||||
Vault password file\&.
|
||||
.RE
|
||||
.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\-\-version\fR
|
||||
.RS 4
|
||||
Show program\(cqs 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
|
||||
ANSIBLE_INVENTORY \(em Override the default ansible inventory file
|
||||
.sp
|
||||
ANSIBLE_LIBRARY \(em Override the default ansible module library path
|
||||
.SH "FILES"
|
||||
|
|
|
@ -34,16 +34,87 @@ The names of one or more YAML format files to run as ansible playbooks.
|
|||
OPTIONS
|
||||
-------
|
||||
|
||||
*-v*, *--verbose*::
|
||||
*-k*, *--ask-pass*::
|
||||
|
||||
Verbose mode, more output from successful actions will be shown. Give
|
||||
up to three times for more output.
|
||||
Prompt for the SSH password instead of assuming key-based
|
||||
authentication with ssh-agent.
|
||||
|
||||
*-K*, *--ask-sudo-pass*::
|
||||
|
||||
Prompt for the password to use for playbook plays that request sudo
|
||||
access, if any.
|
||||
|
||||
*-b*, *--become*::
|
||||
|
||||
Run operations with become (nopasswd implied)
|
||||
|
||||
*--become-method=BECOME_METHOD*::
|
||||
|
||||
Privilege escalation method to use (default=sudo),
|
||||
valid choices: [ sudo | su | pbrun | pfexec | runas ]
|
||||
|
||||
*--become-user=BECOME_USER*::
|
||||
|
||||
Run operations as this user (default=None).
|
||||
|
||||
*-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. Possible options are 'paramiko' (SSH), 'ssh',
|
||||
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* 'VARS', *--extra-vars=*'VARS'::
|
||||
|
||||
Extra variables to inject into a playbook, in key=value key=value format or
|
||||
as quoted 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
|
||||
|
||||
*-i* 'PATH', *--inventory=*'PATH'::
|
||||
|
||||
The 'PATH' to the inventory hosts file, which defaults to
|
||||
'/etc/ansible/hosts'.
|
||||
|
||||
*-l* 'SUBSET', *--limit=*'SUBSET'::
|
||||
|
||||
Further limits the selected host/group patterns.
|
||||
|
||||
*--list-hosts*::
|
||||
|
||||
Outputs a list of matching hosts; does not execute anything else.
|
||||
|
||||
*--list-tags*::
|
||||
|
||||
List all available tags.
|
||||
|
||||
*--list-tasks*::
|
||||
|
||||
List all tasks that would be executed
|
||||
|
||||
*-M* 'DIRECTORY', *--module-path=*'DIRECTORY'::
|
||||
|
||||
|
@ -51,34 +122,38 @@ 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.
|
||||
|
||||
*-e* 'VARS', *--extra-vars=*'VARS'::
|
||||
*--private-key=*'PRIVATE_KEY_FILE'::
|
||||
|
||||
Extra variables to inject into a playbook, in key=value key=value format or
|
||||
as quoted JSON (hashes and arrays).
|
||||
Use this file to authenticate the connection
|
||||
|
||||
*-f* 'NUM', *--forks=*'NUM'::
|
||||
*--skip-tages=*'SKIP_TAGS'::
|
||||
|
||||
Level of parallelism. 'NUM' is specified as an integer, the default is 5.
|
||||
Only run plays and tasks whose tags do not match these values.
|
||||
|
||||
*--start-at-task=*'START_AT'::
|
||||
|
||||
*-k*, *--ask-pass*::
|
||||
Start the playbook at the task matching this name.
|
||||
|
||||
Prompt for the SSH password instead of assuming key-based
|
||||
authentication with ssh-agent.
|
||||
*--step*::
|
||||
|
||||
One-step-at-a-time: confirm each task before running.
|
||||
|
||||
*-K*, *--ask-sudo-pass*::
|
||||
*-S*, --su*::
|
||||
|
||||
Prompt for the password to use for playbook plays that request sudo
|
||||
access, if any.
|
||||
Run operations with su (deprecated, use become)
|
||||
|
||||
*-R SU-USER*, *--su-user=*'SU_USER'::
|
||||
|
||||
run operations with su as this user (default=root)
|
||||
(deprecated, use become)
|
||||
|
||||
*-s*, *--sudo*::
|
||||
|
||||
Run operations with sudo (nopasswd) (deprecated, use become)
|
||||
|
||||
*-U*, 'SUDO_USER', *--sudo-user=*'SUDO_USER'::
|
||||
|
||||
Desired sudo user (default=root).
|
||||
|
||||
*-t*, 'TAGS', *--tags=*'TAGS'::
|
||||
|
||||
Only run plays and tasks tagged with these values.
|
||||
Desired sudo user (default=root) (deprecated, use become).
|
||||
|
||||
*--skip-tags=*'SKIP_TAGS'::
|
||||
|
||||
|
@ -88,48 +163,38 @@ Only run plays and tasks whose tags do not match these values.
|
|||
|
||||
Look for syntax errors in the playbook, but don't run anything
|
||||
|
||||
*--check*::
|
||||
*-t*, 'TAGS', *--tags=*'TAGS'::
|
||||
|
||||
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.
|
||||
Only run plays and tasks tagged with these values.
|
||||
|
||||
*-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 remote user name on playbook steps that do not indicate a
|
||||
user name to run as.
|
||||
|
||||
*-c* 'CONNECTION', *--connection=*'CONNECTION'::
|
||||
*--vault-password-file=*'VAULT_PASSWORD_FILE'::
|
||||
|
||||
Connection type to use. Possible options are 'paramiko' (SSH), 'ssh',
|
||||
and 'local'. 'local' is mostly useful for crontab or kickstarts.
|
||||
Vault password file.
|
||||
|
||||
*-l* 'SUBSET', *--limit=*'SUBSET'::
|
||||
*-v*, *--verbose*::
|
||||
|
||||
Further limits the selected host/group patterns.
|
||||
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.
|
||||
|
||||
ENVIRONMENT
|
||||
-----------
|
||||
|
||||
The following environment variables may be specified.
|
||||
|
||||
ANSIBLE_HOSTS -- Override the default ansible hosts file
|
||||
ANSIBLE_INVENTORY -- Override the default ansible inventory file
|
||||
|
||||
ANSIBLE_LIBRARY -- Override the default ansible module library path
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
'\" t
|
||||
.\" Title: ansible
|
||||
.\" Author: :doctype:manpage
|
||||
.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
|
||||
.\" Date: 05/26/2014
|
||||
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
|
||||
.\" Date: 07/22/2015
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 1.7
|
||||
.\" Source: Ansible %VERSION%
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE" "1" "05/26/2014" "Ansible 1\&.7" "System administration commands"
|
||||
.TH "ANSIBLE" "1" "07/22/2015" "Ansible %VERSION%" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
|
@ -31,7 +31,7 @@
|
|||
ansible-pull \- set up a remote copy of ansible on each managed node
|
||||
.SH "SYNOPSIS"
|
||||
.sp
|
||||
ansible \-d DEST \-U URL [options] [ <filename\&.yml> ]
|
||||
ansible\-pull \-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\&.
|
||||
|
@ -50,14 +50,14 @@ The name of one the YAML format files to run as an ansible playbook\&. This can
|
|||
.RE
|
||||
.SH "OPTIONS"
|
||||
.PP
|
||||
\fB\-d\fR \fIDEST\fR, \fB\-\-directory=\fR\fIDEST\fR
|
||||
\fB\-\-accept\-host\-key\fR
|
||||
.RS 4
|
||||
Directory to checkout repository into\&. If not provided, a subdirectory of ~/\&.ansible/pull/ will be used\&.
|
||||
Adds the hostkey for the repo URL if not already added\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-U\fR \fIURL\fR, \fB\-\-url=\fR\fIURL\fR
|
||||
\fB\-K\fR, \fB\-\-ask\-sudo\-pass\fR
|
||||
.RS 4
|
||||
URL of the playbook repository to checkout\&.
|
||||
Ask for sudo password\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-C\fR \fICHECKOUT\fR, \fB\-\-checkout=\fR\fICHECKOUT\fR
|
||||
|
@ -65,11 +65,26 @@ URL of the playbook repository to checkout\&.
|
|||
Branch/Tag/Commit to checkout\&. If not provided, uses default behavior of module used to check out playbook repository\&.
|
||||
.RE
|
||||
.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\-e\fR \fIEXTRA_VARS\fR, \fB\-\-extra\-vars=\fR\*(AqEXTRA_VARS*
|
||||
.RS 4
|
||||
Set additional variables as key=value or YAML/JSON
|
||||
.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\-h\fR, \fB\-\-help\fR
|
||||
.RS 4
|
||||
Show the help message and exit\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-i\fR \fIPATH\fR, \fB\-\-inventory=\fR\fIPATH\fR
|
||||
.RS 4
|
||||
The
|
||||
|
@ -77,9 +92,11 @@ The
|
|||
to the inventory hosts file\&. This can be a relative path within the checkout\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-purge\fR
|
||||
\fB\-\-key\-file=\fR\fIKEYFILE\fR
|
||||
.RS 4
|
||||
Purge the checkout after the playbook is run\&.
|
||||
Pass
|
||||
\fI\-i <key_file>\fR
|
||||
to the SSH arguments used by git\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-m\fR \fINAME\fR, \fB\-\-module\-name=\fR\fINAME\fR
|
||||
|
@ -89,7 +106,37 @@ Module used to checkout playbook repository\&. Defaults to git\&.
|
|||
.PP
|
||||
\fB\-o\fR, \fB\-\-only\-if\-changed\fR
|
||||
.RS 4
|
||||
Run the playbook only if the repository has changed
|
||||
Only run the playbook if the repository has been updated\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-purge\fR
|
||||
.RS 4
|
||||
Purge the checkout after the playbook is run\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-s\fR \fISLEEP\fR, \fB\-\-sleep=\fR\fISLEEP\fR
|
||||
.RS 4
|
||||
Sleep for random interval (between 0 and SLEEP number of seconds) before starting\&. This is a useful way ot disperse git requests\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-t\fR \fITAGS\fR, \fB\-\-tags=\fR\fITAGS\fR
|
||||
.RS 4
|
||||
Only run plays and tasks tagged with these values\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-U\fR \fIURL\fR, \fB\-\-url=\fR\fIURL\fR
|
||||
.RS 4
|
||||
URL of the playbook repository to checkout\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-vault\-password\-file=\fR\fIVAULT_PASSWORD_FILE\fR
|
||||
.RS 4
|
||||
Vault password file\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-v\fR, \fB\-\-verbose\fR
|
||||
.RS 4
|
||||
Pass \-vvv to ansible\-playbook\&.
|
||||
.RE
|
||||
.SH "AUTHOR"
|
||||
.sp
|
||||
|
|
|
@ -50,19 +50,26 @@ host hostname and finally a playbook named *local.yml*.
|
|||
OPTIONS
|
||||
-------
|
||||
|
||||
*--accept-host-key*::
|
||||
|
||||
Adds the hostkey for the repo URL if not already added.
|
||||
|
||||
*-K*, *--ask-sudo-pass*::
|
||||
|
||||
Ask for sudo password.
|
||||
|
||||
*-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.
|
||||
|
||||
*-U* 'URL', *--url=*'URL'::
|
||||
*-e* 'EXTRA_VARS', *--extra-vars=*'EXTRA_VARS*::
|
||||
|
||||
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.
|
||||
Set additional variables as key=value or YAML/JSON
|
||||
|
||||
*-f*, *--force*::
|
||||
|
||||
|
@ -70,14 +77,17 @@ 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.
|
||||
|
||||
*-h*, *--help*::
|
||||
|
||||
Show the help message and exit.
|
||||
|
||||
*-i* 'PATH', *--inventory=*'PATH'::
|
||||
|
||||
The 'PATH' to the inventory hosts file. This can be a relative path within
|
||||
the checkout.
|
||||
The 'PATH' to the inventory hosts file. This can be a relative path within the checkout.
|
||||
|
||||
*--purge*::
|
||||
*--key-file=*'KEYFILE'::
|
||||
|
||||
Purge the checkout after the playbook is run.
|
||||
Pass '-i <key_file>' to the SSH arguments used by git.
|
||||
|
||||
*-m* 'NAME', *--module-name=*'NAME'::
|
||||
|
||||
|
@ -85,7 +95,32 @@ Module used to checkout playbook repository. Defaults to git.
|
|||
|
||||
*-o*, *--only-if-changed*::
|
||||
|
||||
Run the playbook only if the repository has 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.
|
||||
|
||||
*-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.
|
||||
|
||||
|
||||
AUTHOR
|
||||
------
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
'\" t
|
||||
.\" Title: ansible-vault
|
||||
.\" Author: [see the "AUTHOR" section]
|
||||
.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
|
||||
.\" Date: 05/26/2014
|
||||
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
|
||||
.\" Date: 12/09/2014
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 1.7
|
||||
.\" Source: Ansible 1.9
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE\-VAULT" "1" "05/26/2014" "Ansible 1\&.7" "System administration commands"
|
||||
.TH "ANSIBLE\-VAULT" "1" "12/09/2014" "Ansible 1\&.9" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
'\" t
|
||||
.\" Title: ansible
|
||||
.\" Author: :doctype:manpage
|
||||
.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
|
||||
.\" Date: 05/26/2014
|
||||
.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
|
||||
.\" Date: 07/15/2015
|
||||
.\" Manual: System administration commands
|
||||
.\" Source: Ansible 1.7
|
||||
.\" Source: Ansible %VERSION%
|
||||
.\" Language: English
|
||||
.\"
|
||||
.TH "ANSIBLE" "1" "05/26/2014" "Ansible 1\&.7" "System administration commands"
|
||||
.TH "ANSIBLE" "1" "07/15/2015" "Ansible %VERSION%" "System administration commands"
|
||||
.\" -----------------------------------------------------------------
|
||||
.\" * Define some portability stuff
|
||||
.\" -----------------------------------------------------------------
|
||||
|
@ -43,9 +43,86 @@ A name of a group in the inventory file, a shell\-like glob selecting hosts in i
|
|||
.RE
|
||||
.SH "OPTIONS"
|
||||
.PP
|
||||
\fB\-v\fR, \fB\-\-verbose\fR
|
||||
\fB\-a\fR \*(Aq\fIARGUMENTS\fR\*(Aq, \fB\-\-args=\fR\*(Aq\fIARGUMENTS\fR\*(Aq
|
||||
.RS 4
|
||||
Verbose mode, more output from successful actions will be shown\&. Give up to three times for more output\&.
|
||||
The
|
||||
\fIARGUMENTS\fR
|
||||
to pass to the module\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-ask\-become\-pass\fR
|
||||
.RS 4
|
||||
Ask for privilege escalation password\&.
|
||||
.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\-\-ask\-su\-pass\fR
|
||||
.RS 4
|
||||
Prompt for su password (deprecated, use become)\&.
|
||||
.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\-\-ask\-vault\-pass\fR
|
||||
.RS 4
|
||||
Prompt for vault password\&.
|
||||
.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\-\-become\-method=\fR\fIBECOME_METHOD\fR
|
||||
.RS 4
|
||||
Privilege escalation method to use (default=sudo), valid choices: [ sudo | su | pbrun | pfexec | runas ]
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-become\-user=\fR\fIBECOME_USER\fR
|
||||
.RS 4
|
||||
Run operations as this user (default=None)\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-C\fR, \fB\-\-check\fR
|
||||
.RS 4
|
||||
Don\(cqt make any changes; instead try to predict some of the changes that may occur\&.
|
||||
.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\-e\fR \fIEXTRA_VARS*, \fR\fI\fB\-\-extra\-vars=\fR\fR\fI\*(AqEXTRA_VARS\fR
|
||||
.RS 4
|
||||
Set additional variables as key=value or YAML/JSON\&.
|
||||
.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\-h\fR, \fB\-\-help\fR
|
||||
.RS 4
|
||||
Show help message and exit\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-i\fR \fIPATH\fR, \fB\-\-inventory=\fR\fIPATH\fR
|
||||
|
@ -56,16 +133,19 @@ 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
|
||||
\fB\-l\fR \fISUBSET\fR, \fB\-\-limit=\fR\fISUBSET\fR
|
||||
.RS 4
|
||||
Level of parallelism\&.
|
||||
\fINUM\fR
|
||||
is specified as an integer, the default is 5\&.
|
||||
Further limits the selected host/group patterns\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-private\-key=\fR\fIPRIVATE_KEY_FILE\fR
|
||||
\fB\-l\fR \fI~REGEX\fR, \fB\-\-limit=\fR\fI~REGEX\fR
|
||||
.RS 4
|
||||
Use this file to authenticate the connection\&.
|
||||
Further limits hosts with a regex pattern\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-\-list\-hosts\fR
|
||||
.RS 4
|
||||
Outputs a list of matching hosts; does not execute anything else\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-m\fR \fINAME\fR, \fB\-\-module\-name=\fR\fINAME\fR
|
||||
|
@ -82,33 +162,46 @@ search path to load modules from\&. The default is
|
|||
\fI/usr/share/ansible\fR\&. This can also be set with the ANSIBLE_LIBRARY environment variable\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-a\fR \*(Aq\fIARGUMENTS\fR\*(Aq, \fB\-\-args=\fR\*(Aq\fIARGUMENTS\fR\*(Aq
|
||||
.RS 4
|
||||
The
|
||||
\fIARGUMENTS\fR
|
||||
to pass to the module\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-k\fR, \fB\-\-ask\-pass\fR
|
||||
.RS 4
|
||||
Prompt for the SSH password instead of assuming key\-based authentication with ssh\-agent\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-K\fR, \fB\-\-ask\-sudo\-pass\fR
|
||||
.RS 4
|
||||
Prompt for the password to use with \-\-sudo, if any
|
||||
.RE
|
||||
.PP
|
||||
\fB\-o\fR, \fB\-\-one\-line\fR
|
||||
.RS 4
|
||||
Try to output everything on one line\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-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\-\-private\-key=\fR\fIPRIVATE_KEY_FILE\fR
|
||||
.RS 4
|
||||
Use this file to authenticate the connection\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-S\fR, \fB\-\-su\fR
|
||||
.RS 4
|
||||
Run operations with su (deprecated, use become)\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-R\fR \fISU_USER\fR, \fB\-\-se\-user=\fR\fISUDO_USER\fR
|
||||
.RS 4
|
||||
Run operations with su as this user (default=root) (deprecated, use become)
|
||||
.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\-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\-t\fR \fIDIRECTORY\fR, \fB\-\-tree=\fR\fIDIRECTORY\fR
|
||||
.RS 4
|
||||
Save contents in this output
|
||||
|
@ -121,21 +214,6 @@ 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
|
||||
|
@ -143,32 +221,19 @@ Use this remote
|
|||
instead of the current user\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-U\fR \fISUDO_USERNAME\fR, \fB\-\-sudo\-user=\fR\fISUDO_USERNAME\fR
|
||||
\fB\-\-vault\-password\-file=\fR\fIVAULT_PASSWORD_FILE\fR
|
||||
.RS 4
|
||||
Sudo to
|
||||
\fISUDO_USERNAME\fR
|
||||
instead of root\&. Implies \-\-sudo\&.
|
||||
Vault password file\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-c\fR \fICONNECTION\fR, \fB\-\-connection=\fR\fICONNECTION\fR
|
||||
\fB\-v\fR, \fB\-\-verbose\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\&.
|
||||
Verbose mode, more output from successful actions will be shown\&. Give up to three times for more output\&.
|
||||
.RE
|
||||
.PP
|
||||
\fB\-l\fR \fISUBSET\fR, \fB\-\-limit=\fR\fISUBSET\fR
|
||||
\fB\-\-version\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\&.
|
||||
Show program version number and exit\&.
|
||||
.RE
|
||||
.SH "INVENTORY"
|
||||
.sp
|
||||
|
@ -188,7 +253,7 @@ Ranges of hosts are also supported\&. For more information and additional option
|
|||
.sp
|
||||
The following environment variables may be specified\&.
|
||||
.sp
|
||||
ANSIBLE_HOSTS \(em Override the default ansible hosts file
|
||||
ANSIBLE_INVENTORY \(em Override the default ansible inventory file
|
||||
.sp
|
||||
ANSIBLE_LIBRARY \(em Override the default ansible module library path
|
||||
.sp
|
||||
|
|
|
@ -34,56 +34,119 @@ semicolons.
|
|||
OPTIONS
|
||||
-------
|
||||
|
||||
*-v*, *--verbose*::
|
||||
*-a* \'_ARGUMENTS_', *--args=*\'_ARGUMENTS_'::
|
||||
|
||||
Verbose mode, more output from successful actions will be shown. Give
|
||||
up to three times for more output.
|
||||
The 'ARGUMENTS' to pass to the module.
|
||||
|
||||
*-i* 'PATH', *--inventory=*'PATH'::
|
||||
*--ask-become-pass*::
|
||||
|
||||
The 'PATH' to the inventory hosts file, which defaults to '/etc/ansible/hosts'.
|
||||
Ask for privilege escalation password.
|
||||
|
||||
*-k*, *--ask-pass*::
|
||||
|
||||
Prompt for the SSH password instead of assuming key-based authentication with ssh-agent.
|
||||
|
||||
*--ask-su-pass*::
|
||||
|
||||
Prompt for su password (deprecated, use become).
|
||||
|
||||
*-K*, *--ask-sudo-pass*::
|
||||
|
||||
Prompt for the password to use with --sudo, if any.
|
||||
|
||||
*--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 ]
|
||||
|
||||
*--become-user=*'BECOME_USER'::
|
||||
|
||||
Run operations as this user (default=None).
|
||||
|
||||
*-C*, *--check*::
|
||||
|
||||
Don't make any changes; instead try to predict some of the changes that may occur.
|
||||
|
||||
*-c* 'CONNECTION', *--connection=*'CONNECTION'::
|
||||
|
||||
Connection type to use. Possible options are 'paramiko' (SSH), 'ssh',
|
||||
and 'local'. 'local' is mostly useful for crontab or kickstarts.
|
||||
|
||||
*-e* 'EXTRA_VARS*, *--extra-vars=*'EXTRA_VARS'::
|
||||
|
||||
Set additional variables as key=value or YAML/JSON.
|
||||
|
||||
*-f* 'NUM', *--forks=*'NUM'::
|
||||
|
||||
Level of parallelism. 'NUM' is specified as an integer, the default is 5.
|
||||
|
||||
*--private-key=*'PRIVATE_KEY_FILE'::
|
||||
*-h*, *--help*::
|
||||
|
||||
Use this file to authenticate the connection.
|
||||
Show help message and exit.
|
||||
|
||||
*-i* 'PATH', *--inventory=*'PATH'::
|
||||
|
||||
The 'PATH' to the inventory hosts file, which defaults to '/etc/ansible/hosts'.
|
||||
|
||||
*-l* 'SUBSET', *--limit=*'SUBSET'::
|
||||
|
||||
Further limits the selected host/group patterns.
|
||||
|
||||
*-l* '\~REGEX', *--limit=*'~REGEX'::
|
||||
|
||||
Further limits hosts with a regex pattern.
|
||||
|
||||
*--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.
|
||||
|
||||
*-U* 'SUDO_USERNAME', *--sudo-user=*'SUDO_USERNAME'::
|
||||
|
||||
Sudo to 'SUDO_USERNAME' instead of root. Implies --sudo.
|
||||
|
||||
*-t* 'DIRECTORY', *--tree=*'DIRECTORY'::
|
||||
|
||||
Save contents in this output 'DIRECTORY', with the results saved in a
|
||||
|
@ -93,34 +156,22 @@ 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 remote 'USERNAME' instead of the current user.
|
||||
|
||||
*-U* 'SUDO_USERNAME', *--sudo-user=*'SUDO_USERNAME'::
|
||||
*--vault-password-file=*'VAULT_PASSWORD_FILE'::
|
||||
|
||||
Sudo to 'SUDO_USERNAME' instead of root. Implies --sudo.
|
||||
Vault password file.
|
||||
|
||||
*-c* 'CONNECTION', *--connection=*'CONNECTION'::
|
||||
*-v*, *--verbose*::
|
||||
|
||||
Connection type to use. Possible options are 'paramiko' (SSH), 'ssh',
|
||||
and 'local'. 'local' is mostly useful for crontab or kickstarts.
|
||||
Verbose mode, more output from successful actions will be shown. Give
|
||||
up to three times for more output.
|
||||
|
||||
*-l* 'SUBSET', *--limit=*'SUBSET'::
|
||||
*--version*::
|
||||
|
||||
Further limits the selected host/group patterns.
|
||||
|
||||
*-l* '\~REGEX', *--limit=*'~REGEX'::
|
||||
|
||||
Further limits hosts with a regex pattern.
|
||||
Show program version number and exit.
|
||||
|
||||
INVENTORY
|
||||
---------
|
||||
|
@ -149,7 +200,7 @@ ENVIRONMENT
|
|||
|
||||
The following environment variables may be specified.
|
||||
|
||||
ANSIBLE_HOSTS -- Override the default ansible hosts file
|
||||
ANSIBLE_INVENTORY -- Override the default ansible inventory file
|
||||
|
||||
ANSIBLE_LIBRARY -- Override the default ansible module library path
|
||||
|
||||
|
|
|
@ -13,13 +13,13 @@
|
|||
<hr/>
|
||||
|
||||
<p>
|
||||
© Copyright 2014 <a href="http://ansible.com">Ansible, Inc.</a>.
|
||||
© Copyright 2015 <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="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 %}
|
||||
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 %}
|
||||
|
||||
</footer>
|
||||
|
|
|
@ -113,6 +113,9 @@
|
|||
}
|
||||
</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">
|
||||
|
@ -122,16 +125,32 @@
|
|||
<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:#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 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>
|
||||
<div class="wy-side-nav-search" style="background-color:#64C5C7;height=80px;margin:'auto auto auto auto'">
|
||||
|
||||
<div class="wy-side-nav-search" style="background-color:#5bbdbf;height=80px;margin:'auto auto auto auto'">
|
||||
<!-- <a href="{{ pathto(master_doc) }}" class="icon icon-home"> {{ project }}</a> -->
|
||||
{% include "searchbox.html" %}
|
||||
</div>
|
||||
|
@ -177,15 +196,17 @@
|
|||
<div class="wy-nav-content">
|
||||
<div class="rst-content">
|
||||
|
||||
<!-- temporary image for AnsibleFest SF -->
|
||||
<center>
|
||||
<a href="http://www.ansible.com/ansiblefest-london">
|
||||
<img src="http://www.ansible.com/hs-fs/hub/330046/file-1926586843-png/DL_Folder/ANSIBLEFEST_(2).png" width="500px" height="90px">
|
||||
</a>
|
||||
<br/> <br/>
|
||||
<br/> <br/>
|
||||
</center>
|
||||
|
||||
<!-- AnsibleFest and free eBook preview stuff -->
|
||||
<center>
|
||||
<a href="http://www.ansible.com/tower?utm_source=docs">
|
||||
<img src="http://www.ansible.com/hubfs/Docs_Ads/TowerDocs.png">
|
||||
</a>
|
||||
<a href="http://www.ansible.com/ansible-book?utm_source=docs">
|
||||
<img src="http://www.ansible.com/hubfs/Docs_Ads/Get_2_full_chapters_of_Ansible-_Up_.png">
|
||||
</a>
|
||||
<br/> <br/>
|
||||
<br/> <br/>
|
||||
</center>
|
||||
|
||||
{% include "breadcrumbs.html" %}
|
||||
<div id="page-content">
|
||||
|
|
|
@ -4634,3 +4634,83 @@ 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;
|
||||
}
|
||||
|
|
BIN
docsite/_themes/srtd/static/images/logo_invert.png
Normal file
BIN
docsite/_themes/srtd/static/images/logo_invert.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
|
@ -88,14 +88,7 @@ if __name__ == '__main__':
|
|||
print " Run 'make viewdocs' to build and then preview in a web browser."
|
||||
sys.exit(0)
|
||||
|
||||
# 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()
|
||||
build_rst_docs()
|
||||
|
||||
if "view" in sys.argv:
|
||||
import webbrowser
|
||||
|
|
|
@ -25,7 +25,7 @@ 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 ``-`` (dash) character::
|
||||
with a ``"- "`` (a dash and a space)::
|
||||
|
||||
---
|
||||
# A list of tasty fruits
|
||||
|
@ -34,7 +34,7 @@ with a ``-`` (dash) character::
|
|||
- Strawberry
|
||||
- Mango
|
||||
|
||||
A dictionary is represented in a simple ``key:`` and ``value`` form::
|
||||
A dictionary is represented in a simple ``key: value`` form (the colon must be followed by a space)::
|
||||
|
||||
---
|
||||
# An employee record
|
||||
|
@ -85,11 +85,11 @@ That's all you really need to know about YAML to start writing
|
|||
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"
|
||||
|
||||
|
@ -107,7 +107,7 @@ with a "{", YAML will think it is a dictionary, so you must quote it, like so::
|
|||
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/tree/devel/examples/playbooks>`_
|
||||
`Github examples directory <https://github.com/ansible/ansible-examples>`_
|
||||
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
|
||||
|
|
87
docsite/rst/become.rst
Normal file
87
docsite/rst/become.rst
Normal file
|
@ -0,0 +1,87 @@
|
|||
Ansible 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'
|
||||
|
||||
|
||||
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 ]
|
||||
|
||||
--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.
|
||||
|
||||
|
||||
|
||||
.. note:: Privilege escalation methods must also be supported by the connection plugin used, most will warn if they do not, some will just ignore it as they always run as root (jail, chroot, etc).
|
||||
|
||||
.. note:: 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).
|
||||
|
||||
.. note:: 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. So if you have '/sbin/sevice' or '/bin/chmod' as the allowed commands this will fail with ansible.
|
||||
|
||||
.. 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
|
||||
|
50
docsite/rst/common_return_values.rst
Normal file
50
docsite/rst/common_return_values.rst
Normal file
|
@ -0,0 +1,50 @@
|
|||
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
|
|
@ -62,13 +62,13 @@ 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 <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.
|
||||
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 <github.com/ansible/ansible-modules-core>`_ or `ansible-modules-extras <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.
|
||||
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://raw2.github.com/ansible/ansible/devel/ISSUE_TEMPLATE.md>`_ to provide all relevant information, regardless of what repo you are filing a ticket against.
|
||||
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.
|
||||
|
@ -132,39 +132,63 @@ 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 <http://github.com/ansible/ansible>`_ for the core application, and two sub repos ansible/ansible-modules-core and 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 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>`_.
|
||||
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).
|
||||
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
|
||||
“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.
|
||||
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 requrie 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.
|
||||
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 "test/integration" directory.
|
||||
|
||||
We’ll then review your contributions and engage with you about questions and so on.
|
||||
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.
|
||||
|
||||
As we have a very large and active community, so it may take awhile to get your contributions
|
||||
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 be made against the 'devel' branch.
|
||||
Patches should always 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
|
||||
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+,
|
||||
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.
|
||||
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!
|
||||
|
@ -175,32 +199,34 @@ 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.
|
||||
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
|
||||
Our most important task however is enabling all the great things that happen in the Ansible
|
||||
community, including organizing software releases of Ansible. For more information about
|
||||
any of these things, contact info@ansible.com
|
||||
|
||||
On IRC, you can find us as mdehaan, jimi_c, abadger1999, Tybstar, and others. On the mailing list,
|
||||
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
|
||||
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 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 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 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.
|
||||
|
||||
To subscribe to a group from a non-google account, you can email the subscription address, for
|
||||
To subscribe to a group from a non-google account, you can email the subscription address, for
|
||||
example ansible-devel+subscribe@googlegroups.com.
|
||||
|
||||
Release Numbering
|
||||
|
@ -208,9 +234,9 @@ 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.
|
||||
bugfixes.
|
||||
|
||||
Typically we don't do dot releases for minor bugfixes (reserving these for larger items),
|
||||
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.
|
||||
|
||||
|
@ -219,7 +245,7 @@ Releases are also given code names based on Van Halen songs, that no one really
|
|||
Tower Support Questions
|
||||
-----------------------
|
||||
|
||||
Ansible `Tower <http://ansible.com/tower>`_ is a UI, Server, and REST endpoint for Ansible, produced by Ansible, Inc.
|
||||
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.
|
||||
|
@ -227,7 +253,7 @@ channel or the general project mailing list.
|
|||
IRC Channel
|
||||
-----------
|
||||
|
||||
Ansible has an IRC channel #ansible on irc.freenode.net.
|
||||
Ansible has an IRC channel #ansible on irc.freenode.net.
|
||||
|
||||
Notes on Priority Flags
|
||||
-----------------------
|
||||
|
@ -241,10 +267,10 @@ 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.
|
||||
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.
|
||||
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.
|
||||
|
||||
|
@ -264,18 +290,18 @@ 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).
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
|
|
|
@ -18,7 +18,7 @@ The directory "./library", alongside your top level playbooks, is also automatic
|
|||
added as a search directory.
|
||||
|
||||
Should you develop an interesting Ansible module, consider sending a pull request to the
|
||||
`modules-extras project <http://github.com/ansible/ansible-modules-extras>`_. There's also a core
|
||||
`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.
|
||||
|
@ -48,7 +48,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 `time.py`::
|
||||
Ok, let's get going with an example. We'll use Python. For starters, save this as a file named `timetest.py`::
|
||||
|
||||
#!/usr/bin/python
|
||||
|
||||
|
@ -73,7 +73,7 @@ There's a useful test script in the source checkout for ansible::
|
|||
|
||||
Let's run the script you just wrote with that::
|
||||
|
||||
ansible/hacking/test-module -m ./time.py
|
||||
ansible/hacking/test-module -m ./timetest.py
|
||||
|
||||
You should see output that looks something like this::
|
||||
|
||||
|
@ -238,7 +238,8 @@ The 'group' and 'user' modules are reasonably non-trivial and showcase what this
|
|||
Key parts include always ending the module file with::
|
||||
|
||||
from ansible.module_utils.basic import *
|
||||
main()
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
And instantiating the module class like::
|
||||
|
||||
|
@ -262,7 +263,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.md5(path). See
|
||||
There are also other useful functions in the module class, such as module.sha1(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
|
||||
|
@ -291,7 +292,7 @@ will evaluate to True when check mode is enabled. For example::
|
|||
)
|
||||
|
||||
if module.check_mode:
|
||||
# Check if any changes would be made by don't actually make those changes
|
||||
# Check if any changes would be made but 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
|
||||
|
@ -309,8 +310,7 @@ You should also never do this in a module::
|
|||
|
||||
print "some status message"
|
||||
|
||||
Because the output is supposed to be valid JSON. Except that's not quite true,
|
||||
but we'll get to that later.
|
||||
Because the output is supposed to be valid JSON.
|
||||
|
||||
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 +343,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 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.
|
||||
* Modules should output valid JSON only. 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,21 +351,6 @@ 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
|
||||
|
@ -386,7 +371,7 @@ See an example documentation string in the checkout under `examples/DOCUMENTATIO
|
|||
|
||||
Include it in your module file like this::
|
||||
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/python
|
||||
# Copyright header....
|
||||
|
||||
DOCUMENTATION = '''
|
||||
|
@ -457,14 +442,125 @@ Getting Your Module Into Ansible
|
|||
````````````````````````````````
|
||||
|
||||
High-quality modules with minimal dependencies
|
||||
can be included in the ansible, but modules (just due to the programming
|
||||
can be included in Ansible, but 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 change to be promoted to 'core' status, which
|
||||
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 its 'null'
|
||||
* `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
|
||||
* The 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 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
|
||||
* Sometimes you want to split the module, specially if you are adding a list/info state, you want a _facts version
|
||||
* If you are asking 'how can i have a module execute other modules' ... you want to write a role
|
||||
|
||||
|
||||
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 commmon code, avoid duplication
|
||||
* start with::
|
||||
|
||||
#!powershell
|
||||
|
||||
then::
|
||||
<GPL header>
|
||||
then::
|
||||
# WANT_JSON
|
||||
# POWERSHELL_COMMON
|
||||
|
||||
* Arguments:
|
||||
* Try and use state present and state absent like other modules
|
||||
* You need to check that all your mandatory args are present::
|
||||
|
||||
If ($params.state) {
|
||||
$state = $params.state.ToString().ToLower()
|
||||
If (($state -ne 'started') -and ($state -ne 'stopped') -and ($state -ne 'restarted')) {
|
||||
Fail-Json $result "state is '$state'; must be 'started', 'stopped', or 'restarted'"
|
||||
}
|
||||
}
|
||||
|
||||
* 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
|
||||
'''
|
||||
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`modules`
|
||||
|
@ -473,8 +569,10 @@ gives them slightly higher development priority (though they'll work in exactly
|
|||
Learn about developing plugins
|
||||
:doc:`developing_api`
|
||||
Learn about the Python API for playbook and task execution
|
||||
`GitHub modules directory <https://github.com/ansible/ansible/tree/devel/library>`_
|
||||
`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>`_
|
||||
|
|
|
@ -21,7 +21,7 @@ Carrier Pigeon?) it's as simple as copying the format of one of the existing mod
|
|||
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 an later. Previous versions did not support 'smart'.
|
||||
|
||||
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.
|
||||
More documentation on writing connection plugins is pending, though you can jump into `lib/ansible/plugins/connections <https://github.com/ansible/ansible/tree/devel/lib/ansible/plugins/connections>`_ 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 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
|
||||
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
|
||||
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 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
|
||||
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
|
||||
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/runner/filter_plugins/ <https://github.com/ansible/ansible/tree/devel/lib/ansible/runner/filter_plugins>`_ for details.
|
||||
Jump into `lib/ansible/plugins/filter <https://github.com/ansible/ansible/tree/devel/lib/ansible/plugins/filter>`_ 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 `plugins/callbacks <https://github.com/ansible/ansible/tree/devel/plugins/callbacks>`_.
|
||||
Example callbacks are shown in `lib/ansible/plugins/callback <https://github.com/ansible/ansible/tree/devel/lib/ansible/plugins/callback>`_.
|
||||
|
||||
The `log_plays
|
||||
<https://github.com/ansible/ansible/blob/devel/plugins/callbacks/log_plays.py>`_
|
||||
<https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/callback/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/plugins/callbacks/mail.py>`_
|
||||
<https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/callback/mail.py>`_
|
||||
callback sends email when playbooks complete.
|
||||
|
||||
The `osx_say
|
||||
<https://github.com/ansible/ansible/blob/devel/plugins/callbacks/osx_say.py>`_
|
||||
<https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/callback/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.
|
||||
|
|
|
@ -29,12 +29,13 @@ and then commenting on that particular issue on GitHub. Here's how:
|
|||
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 neccessary tools required to run our test
|
||||
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::
|
||||
|
||||
|
|
|
@ -3,7 +3,18 @@ Frequently Asked Questions
|
|||
|
||||
Here are some commonly-asked questions and their answers.
|
||||
|
||||
.. _users_and_ports:
|
||||
.. _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 playbook level::
|
||||
|
||||
environment:
|
||||
PATH: "{{ ansible_env.PATH }}:/thingy/bin"
|
||||
SOME: value
|
||||
|
||||
|
||||
|
||||
How do I handle different machines needing different user accounts or ports to log in with?
|
||||
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
|
|
@ -8,9 +8,9 @@ Ansible Galaxy
|
|||
The Website
|
||||
```````````
|
||||
|
||||
The website `Ansible Galaxy <http://galaxy.ansible.com>`_, is a free site for finding, downloading, rating, and reviewing all kinds of community developed Ansible roles and can be a great way to get a jumpstart on your automation projects.
|
||||
The website `Ansible Galaxy <https://galaxy.ansible.com>`_, is a free site for finding, downloading, rating, and reviewing all kinds of community developed Ansible roles and can be a great way to get a jumpstart on your automation projects.
|
||||
|
||||
You can sign up with social auth, and use the download client 'ansible-galaxy' is included in Ansible 1.4.2 and later.
|
||||
You can sign up with social auth and use the download client 'ansible-galaxy' which is included in Ansible 1.4.2 and later.
|
||||
|
||||
Read the "About" page on the Galaxy site for more information.
|
||||
|
||||
|
|
|
@ -6,120 +6,146 @@ Amazon Web Services Guide
|
|||
Introduction
|
||||
````````````
|
||||
|
||||
.. note:: This section of the documentation is under construction. We are in the process of adding more examples about all of the EC2 modules
|
||||
and how they work together. There's also an ec2 example in the language_features directory of `the ansible-examples github repository <http://github.com/ansible/ansible-examples/>`_ that you may wish to consult. Once complete, there will also be new examples of ec2 in ansible-examples.
|
||||
|
||||
Ansible contains a number of core modules for interacting with Amazon Web Services (AWS). These also work with Eucalyptus, which is an AWS compatible private cloud solution. There are other supported cloud types, but this documentation chapter is about AWS API clouds. The purpose of this
|
||||
Ansible contains a number of modules for controlling Amazon Web Services (AWS). The purpose of this
|
||||
section is to explain how to put Ansible modules together (and use inventory scripts) to use Ansible in AWS context.
|
||||
|
||||
Requirements for the AWS modules are minimal. All of the modules require and are tested against boto 2.5 or higher. You'll need this Python module installed on the execution host. If you are using Red Hat Enterprise Linux or CentOS, install boto from `EPEL <http://fedoraproject.org/wiki/EPEL>`_:
|
||||
Requirements for the AWS modules are minimal.
|
||||
|
||||
.. code-block:: bash
|
||||
All of the modules require and are tested against recent versions of boto. You'll need this Python module installed on your control machine. Boto can be installed from your OS distribution or python's "pip install boto".
|
||||
|
||||
$ yum install python-boto
|
||||
Whereas classically ansible will execute tasks in its host loop against multiple remote machines, most cloud-control steps occur on your local machine with reference to the regions to control.
|
||||
|
||||
You can also install it via pip if you want.
|
||||
|
||||
The following steps will often execute outside the host loop, so it makes sense to add localhost to inventory. Ansible
|
||||
may not require this step in the future::
|
||||
|
||||
[local]
|
||||
localhost
|
||||
|
||||
And in your playbook steps we'll typically be using the following pattern for provisioning steps::
|
||||
In your playbook steps we'll typically be using the following pattern for provisioning steps::
|
||||
|
||||
- hosts: localhost
|
||||
connection: local
|
||||
gather_facts: False
|
||||
tasks:
|
||||
- ...
|
||||
|
||||
.. _aws_authentication:
|
||||
|
||||
Authentication
|
||||
``````````````
|
||||
|
||||
Authentication with the AWS-related modules is handled by either
|
||||
specifying your access and secret key as ENV variables or module arguments.
|
||||
|
||||
For environment variables::
|
||||
|
||||
export AWS_ACCESS_KEY_ID='AK123'
|
||||
export AWS_SECRET_ACCESS_KEY='abc123'
|
||||
|
||||
For storing these in a vars_file, ideally encrypted with ansible-vault::
|
||||
|
||||
---
|
||||
ec2_access_key: "--REMOVED--"
|
||||
ec2_secret_key: "--REMOVED--"
|
||||
|
||||
.. _aws_provisioning:
|
||||
|
||||
Provisioning
|
||||
````````````
|
||||
|
||||
The ec2 module provides the ability to provision instances within EC2. Typically the provisioning task will be performed against your Ansible master server in a play that operates on localhost using the ``local`` connection type. If you are doing an EC2 operation mid-stream inside a regular play operating on remote hosts, you may want to use the ``local_action`` keyword for that particular task. Read :doc:`playbooks_delegation` for more about local actions.
|
||||
The ec2 module provisions and de-provisions instances within EC2.
|
||||
|
||||
.. note::
|
||||
An example of making sure there are only 5 instances tagged 'Demo' in EC2 follows.
|
||||
|
||||
Authentication with the AWS-related modules is handled by either
|
||||
specifying your access and secret key as ENV variables or passing
|
||||
them as module arguments.
|
||||
In the example below, the "exact_count" of instances is set to 5. This means if there are 0 instances already existing, then
|
||||
5 new instances would be created. If there were 2 instances, only 3 would be created, and if there were 8 instances, 3 instances would
|
||||
be terminated.
|
||||
|
||||
.. note::
|
||||
What is being counted is specified by the "count_tag" parameter. The parameter "instance_tags" is used to apply tags to the newly created
|
||||
instance.::
|
||||
|
||||
To talk to specific endpoints, the environmental variable EC2_URL
|
||||
can be set. This is useful if using a private cloud like Eucalyptus,
|
||||
exporting the variable as EC2_URL=https://myhost:8773/services/Eucalyptus.
|
||||
This can be set using the 'environment' keyword in Ansible if you like.
|
||||
# demo_setup.yml
|
||||
|
||||
Here is an example of provisioning a number of instances in ad-hoc mode:
|
||||
- hosts: localhost
|
||||
connection: local
|
||||
gather_facts: False
|
||||
|
||||
.. code-block:: bash
|
||||
tasks:
|
||||
|
||||
# ansible localhost -m ec2 -a "image=ami-6e649707 instance_type=m1.large keypair=mykey group=webservers wait=yes" -c local
|
||||
- name: Provision a set of instances
|
||||
ec2:
|
||||
key_name: my_key
|
||||
group: test
|
||||
instance_type: t2.micro
|
||||
image: "{{ ami_id }}"
|
||||
wait: true
|
||||
exact_count: 5
|
||||
count_tag:
|
||||
Name: Demo
|
||||
instance_tags:
|
||||
Name: Demo
|
||||
register: ec2
|
||||
|
||||
In a play, this might look like (assuming the parameters are held as vars)::
|
||||
The data about what instances are created is being saved by the "register" keyword in the variable named "ec2".
|
||||
|
||||
From this, we'll use the add_host module to dynamically create a host group consisting of these new instances. This facilitates performing configuration actions on the hosts immediately in a subsequent task.::
|
||||
|
||||
# demo_setup.yml
|
||||
|
||||
- hosts: localhost
|
||||
connection: local
|
||||
gather_facts: False
|
||||
|
||||
tasks:
|
||||
|
||||
- name: Provision a set of instances
|
||||
ec2:
|
||||
key_name: my_key
|
||||
group: test
|
||||
instance_type: t2.micro
|
||||
image: "{{ ami_id }}"
|
||||
wait: true
|
||||
exact_count: 5
|
||||
count_tag:
|
||||
Name: Demo
|
||||
instance_tags:
|
||||
Name: Demo
|
||||
register: ec2
|
||||
|
||||
- name: Add all instance public IPs to host group
|
||||
add_host: hostname={{ item.public_ip }} groups=ec2hosts
|
||||
with_items: ec2.instances
|
||||
|
||||
With the host group now created, a second play at the bottom of the the same provisioning playbook file might now have some configuration steps::
|
||||
|
||||
# demo_setup.yml
|
||||
|
||||
tasks:
|
||||
- name: Provision a set of instances
|
||||
ec2: >
|
||||
keypair={{mykeypair}}
|
||||
group={{security_group}}
|
||||
instance_type={{instance_type}}
|
||||
image={{image}}
|
||||
wait=true
|
||||
count={{number}}
|
||||
register: ec2
|
||||
hosts: localhost
|
||||
# ... AS ABOVE ...
|
||||
|
||||
|
||||
By registering the return its then possible to dynamically create a host group consisting of these new instances. This facilitates performing configuration actions on the hosts immediately in a subsequent task::
|
||||
|
||||
- name: Add all instance public IPs to host group
|
||||
add_host: hostname={{ item.public_ip }} groupname=ec2hosts
|
||||
with_items: ec2.instances
|
||||
|
||||
With the host group now created, a second play in your provision playbook might now have some configuration steps::
|
||||
|
||||
- name: Configuration play
|
||||
hosts: ec2hosts
|
||||
- hosts: ec2hosts
|
||||
name: configuration play
|
||||
user: ec2-user
|
||||
gather_facts: true
|
||||
|
||||
tasks:
|
||||
- name: Check NTP service
|
||||
service: name=ntpd state=started
|
||||
|
||||
Rather than include configuration inline, you may also choose to just do it as a task include or a role.
|
||||
|
||||
The method above ties the configuration of a host with the provisioning step. This isn't always ideal and leads us onto the next section.
|
||||
|
||||
.. _aws_advanced:
|
||||
|
||||
Advanced Usage
|
||||
``````````````
|
||||
- name: Check NTP service
|
||||
service: name=ntpd state=started
|
||||
|
||||
.. _aws_host_inventory:
|
||||
|
||||
Host Inventory
|
||||
++++++++++++++
|
||||
``````````````
|
||||
|
||||
Once your nodes are spun up, you'll probably want to talk to them again. The best way to handle this is to use the ec2 inventory plugin.
|
||||
Once your nodes are spun up, you'll probably want to talk to them again. With a cloud setup, it's best to not maintain a static list of cloud hostnames
|
||||
in text files. Rather, the best way to handle this is to use the ec2 dynamic inventory script.
|
||||
|
||||
Even for larger environments, you might have nodes spun up from Cloud Formations or other tooling. You don't have to use Ansible to spin up guests. Once these are created and you wish to configure them, the EC2 API can be used to return system grouping with the help of the EC2 inventory script. This script can be used to group resources by their security group or tags. Tagging is highly recommended in EC2 and can provide an easy way to sort between host groups and roles. The inventory script is documented doc:`api` section.
|
||||
This will also dynamically select nodes that were even created outside of Ansible, and allow Ansible to manage them.
|
||||
|
||||
You may wish to schedule a regular refresh of the inventory cache to accommodate for frequent changes in resources:
|
||||
See the doc:`aws_example` for how to use this, then flip back over to this chapter.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# ./ec2.py --refresh-cache
|
||||
.. _aws_tags_and_groups:
|
||||
|
||||
Put this into a crontab as appropriate to make calls from your Ansible master server to the EC2 API endpoints and gather host information. The aim is to keep the view of hosts as up-to-date as possible, so schedule accordingly. Playbook calls could then also be scheduled to act on the refreshed hosts inventory after each refresh. This approach means that machine images can remain "raw", containing no payload and OS-only. Configuration of the workload is handled entirely by Ansible.
|
||||
Tags And Groups And Variables
|
||||
`````````````````````````````
|
||||
|
||||
Tags
|
||||
++++
|
||||
|
||||
There's a feature in the ec2 inventory script where hosts tagged with
|
||||
certain keys and values automatically appear in certain groups.
|
||||
When using the ec2 inventory script, hosts automatically appear in groups based on how they are tagged in EC2.
|
||||
|
||||
For instance, if a host is given the "class" tag with the value of "webserver",
|
||||
it will be automatically discoverable via a dynamic group like so::
|
||||
|
@ -128,178 +154,83 @@ it will be automatically discoverable via a dynamic group like so::
|
|||
tasks:
|
||||
- ping
|
||||
|
||||
Using this philosophy can be a great way to manage groups dynamically, without
|
||||
having to maintain separate inventory.
|
||||
Using this philosophy can be a great way to keep systems separated by the function they perform.
|
||||
|
||||
In this example, if we wanted to define variables that are automatically applied to each machine tagged with the 'class' of 'webserver', 'group_vars'
|
||||
in ansible can be used. See :ref:`splitting_out_vars`.
|
||||
|
||||
Similar groups are available for regions and other classifications, and can be similarly assigned variables using the same mechanism.
|
||||
|
||||
.. _aws_pull:
|
||||
|
||||
Pull Configuration
|
||||
++++++++++++++++++
|
||||
Autoscaling with Ansible Pull
|
||||
`````````````````````````````
|
||||
|
||||
For some the delay between refreshing host information and acting on that host information (i.e. running Ansible tasks against the hosts) may be too long. This may be the case in such scenarios where EC2 AutoScaling is being used to scale the number of instances as a result of a particular event. Such an event may require that hosts come online and are configured as soon as possible (even a 1 minute delay may be undesirable). Its possible to pre-bake machine images which contain the necessary ansible-pull script and components to pull and run a playbook via git. The machine images could be configured to run ansible-pull upon boot as part of the bootstrapping procedure.
|
||||
Amazon Autoscaling features automatically increase or decrease capacity based on load. There are also Ansible ansibles shown in the cloud documentation that
|
||||
can configure autoscaling policy.
|
||||
|
||||
When nodes come online, it may not be sufficient to wait for the next cycle of an ansible command to come along and configure that node.
|
||||
|
||||
To do this, pre-bake machine images which contain the necessary ansible-pull invocation. Ansible-pull is a command line tool that fetches a playbook from a git server and runs it locally.
|
||||
|
||||
One of the challenges of this approach is that there needs to be a centralized way to store data about the results of pull commands in an autoscaling context.
|
||||
For this reason, the autoscaling solution provided below in the next section can be a better approach.
|
||||
|
||||
Read :ref:`ansible-pull` for more information on pull-mode playbooks.
|
||||
|
||||
(Various developments around Ansible are also going to make this easier in the near future. Stay tuned!)
|
||||
|
||||
.. _aws_autoscale:
|
||||
|
||||
Autoscaling with Ansible Tower
|
||||
++++++++++++++++++++++++++++++
|
||||
``````````````````````````````
|
||||
|
||||
:doc:`tower` also contains a very nice feature for auto-scaling use cases. In this mode, a simple curl script can call
|
||||
a defined URL and the server will "dial out" to the requester and configure an instance that is spinning up. This can be a great way
|
||||
to reconfigure ephemeral nodes. See the Tower documentation for more details. Click on the Tower link in the sidebar for details.
|
||||
to reconfigure ephemeral nodes. See the Tower install and product documentation for more details.
|
||||
|
||||
A benefit of using the callback in Tower over pull mode is that job results are still centrally recorded and less information has to be shared
|
||||
with remote hosts.
|
||||
|
||||
.. _aws_use_cases:
|
||||
|
||||
Use Cases
|
||||
`````````
|
||||
|
||||
This section covers some usage examples built around a specific use case.
|
||||
|
||||
.. _aws_cloudformation_example:
|
||||
|
||||
Example 1
|
||||
+++++++++
|
||||
Ansible With (And Versus) CloudFormation
|
||||
````````````````````````````````````````
|
||||
|
||||
Example 1: I'm using CloudFormation to deploy a specific infrastructure stack. I'd like to manage configuration of the instances with Ansible.
|
||||
CloudFormation is a Amazon technology for defining a cloud stack as a JSON document.
|
||||
|
||||
Provision instances with your tool of choice and consider using the inventory plugin to group hosts based on particular tags or security group. Consider tagging instances you wish to managed with Ansible with a suitably unique key=value tag.
|
||||
Ansible modules provide an easier to use interface than CloudFormation in many examples, without defining a complex JSON document.
|
||||
This is recommended for most users.
|
||||
|
||||
.. note:: Ansible also has a cloudformation module you may wish to explore.
|
||||
However, for users that have decided to use CloudFormation, there is an Ansible module that can be used to apply a CloudFormation template
|
||||
to Amazon.
|
||||
|
||||
.. _aws_autoscale_example:
|
||||
When using Ansible with CloudFormation, typically Ansible will be used with a tool like Packer to build images, and CloudFormation will launch
|
||||
those images, or ansible will be invoked through user data once the image comes online, or a combination of the two.
|
||||
|
||||
Example 2
|
||||
+++++++++
|
||||
Please see the examples in the Ansible CloudFormation module for more details.
|
||||
|
||||
Example 2: I'm using AutoScaling to dynamically scale up and scale down the number of instances. This means the number of hosts is constantly fluctuating but I'm letting EC2 automatically handle the provisioning of these instances. I don't want to fully bake a machine image, I'd like to use Ansible to configure the hosts.
|
||||
.. _aws_image_build:
|
||||
|
||||
There are several approaches to this use case. The first is to use the inventory plugin to regularly refresh host information and then target hosts based on the latest inventory data. The second is to use ansible-pull triggered by a user-data script (specified in the launch configuration) which would then mean that each instance would fetch Ansible and the latest playbook from a git repository and run locally to configure itself. You could also use the Tower callback feature.
|
||||
AWS Image Building With Ansible
|
||||
```````````````````````````````
|
||||
|
||||
.. _aws_builds:
|
||||
Many users may want to have images boot to a more complete configuration rather than configuring them entirely after instantiation. To do this,
|
||||
one of many programs can be used with Ansible playbooks to define and upload a base image, which will then get its own AMI ID for usage with
|
||||
the ec2 module or other Ansible AWS modules such as ec2_asg or the cloudformation module. Possible tools include Packer, aminator, and Ansible's
|
||||
ec2_ami module.
|
||||
|
||||
Example 3
|
||||
+++++++++
|
||||
Generally speaking, we find most users using Packer.
|
||||
|
||||
Example 3: I don't want to use Ansible to manage my instances but I'd like to consider using Ansible to build my fully-baked machine images.
|
||||
`Documentation for the Ansible Packer provisioner can be found here <https://www.packer.io/docs/provisioners/ansible-local.html>`_.
|
||||
|
||||
There's nothing to stop you doing this. If you like working with Ansible's playbook format then writing a playbook to create an image; create an image file with dd, give it a filesystem and then install packages and finally chroot into it for further configuration. Ansible has the 'chroot' plugin for this purpose, just add the following to your inventory file::
|
||||
If you do not want to adopt Packer at this time, configuring a base-image with Ansible after provisioning (as shown above) is acceptable.
|
||||
|
||||
/chroot/path ansible_connection=chroot
|
||||
.. _aws_next_steps:
|
||||
|
||||
And in your playbook::
|
||||
|
||||
hosts: /chroot/path
|
||||
|
||||
Example 4
|
||||
+++++++++
|
||||
|
||||
How would I create a new ec2 instance, provision it and then destroy it all in the same play?
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# Use the ec2 module to create a new host and then add
|
||||
# it to a special "ec2hosts" group.
|
||||
|
||||
- hosts: localhost
|
||||
connection: local
|
||||
gather_facts: False
|
||||
vars:
|
||||
ec2_access_key: "--REMOVED--"
|
||||
ec2_secret_key: "--REMOVED--"
|
||||
keypair: "mykeyname"
|
||||
instance_type: "t1.micro"
|
||||
image: "ami-d03ea1e0"
|
||||
group: "mysecuritygroup"
|
||||
region: "us-west-2"
|
||||
zone: "us-west-2c"
|
||||
tasks:
|
||||
- name: make one instance
|
||||
ec2: image={{ image }}
|
||||
instance_type={{ instance_type }}
|
||||
aws_access_key={{ ec2_access_key }}
|
||||
aws_secret_key={{ ec2_secret_key }}
|
||||
keypair={{ keypair }}
|
||||
instance_tags='{"foo":"bar"}'
|
||||
region={{ region }}
|
||||
group={{ group }}
|
||||
wait=true
|
||||
register: ec2_info
|
||||
|
||||
- debug: var=ec2_info
|
||||
- debug: var=item
|
||||
with_items: ec2_info.instance_ids
|
||||
|
||||
- add_host: hostname={{ item.public_ip }} groupname=ec2hosts
|
||||
with_items: ec2_info.instances
|
||||
|
||||
- name: wait for instances to listen on port:22
|
||||
wait_for:
|
||||
state=started
|
||||
host={{ item.public_dns_name }}
|
||||
port=22
|
||||
with_items: ec2_info.instances
|
||||
|
||||
|
||||
# Connect to the node and gather facts,
|
||||
# including the instance-id. These facts
|
||||
# are added to inventory hostvars for the
|
||||
# duration of the playbook's execution
|
||||
# Typical "provisioning" tasks would go in
|
||||
# this playbook.
|
||||
|
||||
- hosts: ec2hosts
|
||||
gather_facts: True
|
||||
user: ec2-user
|
||||
sudo: True
|
||||
tasks:
|
||||
|
||||
# fetch instance data from the metadata servers in ec2
|
||||
- ec2_facts:
|
||||
|
||||
# show all known facts for this host
|
||||
- debug: var=hostvars[inventory_hostname]
|
||||
|
||||
# just show the instance-id
|
||||
- debug: msg="{{ hostvars[inventory_hostname]['ansible_ec2_instance_id'] }}"
|
||||
|
||||
|
||||
# Using the instanceid, call the ec2 module
|
||||
# locally to remove the instance by declaring
|
||||
# its state is "absent"
|
||||
|
||||
- hosts: ec2hosts
|
||||
gather_facts: True
|
||||
connection: local
|
||||
vars:
|
||||
ec2_access_key: "--REMOVED--"
|
||||
ec2_secret_key: "--REMOVED--"
|
||||
region: "us-west-2"
|
||||
tasks:
|
||||
- name: destroy all instances
|
||||
ec2: state='absent'
|
||||
aws_access_key={{ ec2_access_key }}
|
||||
aws_secret_key={{ ec2_secret_key }}
|
||||
region={{ region }}
|
||||
instance_ids={{ item }}
|
||||
wait=true
|
||||
with_items: hostvars[inventory_hostname]['ansible_ec2_instance_id']
|
||||
|
||||
|
||||
.. note:: more examples of this are pending. You may also be interested in the ec2_ami module for taking AMIs of running instances.
|
||||
|
||||
.. _aws_pending:
|
||||
|
||||
Pending Information
|
||||
```````````````````
|
||||
|
||||
In the future look here for more topics.
|
||||
Next Steps: Explore Modules
|
||||
```````````````````````````
|
||||
|
||||
Ansible ships with lots of modules for configuring a wide array of EC2 services. Browse the "Cloud" category of the module
|
||||
documentation for a full list with examples.
|
||||
|
||||
.. seealso::
|
||||
|
||||
|
@ -309,7 +240,7 @@ In the future look here for more topics.
|
|||
An introduction to playbooks
|
||||
:doc:`playbooks_delegation`
|
||||
Delegation, useful for working with loud balancers, clouds, and locally executed steps.
|
||||
`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
|
||||
|
|
|
@ -22,7 +22,7 @@ The GCE modules all require the apache-libcloud module, which you can install fr
|
|||
Credentials
|
||||
-----------
|
||||
|
||||
To work with the GCE modules, you'll first need to get some credentials. You can create new one from the `console <https://console.developers.google.com/>`_ by going to the "APIs and Auth" section and choosing to create a new client ID for a service account. Once you've created a new client ID and downloaded the generated private key (in the `pkcs12 format <http://en.wikipedia.org/wiki/PKCS_12>`_), you'll need to convert the key by running the following command:
|
||||
To work with the GCE modules, you'll first need to get some credentials. You can create new one from the `console <https://console.developers.google.com/>`_ by going to the "APIs and Auth" section and choosing to create a new client ID for a service account. Once you've created a new client ID and downloaded (you must click **Generate new P12 Key**) the generated private key (in the `pkcs12 format <http://en.wikipedia.org/wiki/PKCS_12>`_), you'll need to convert the key by running the following command:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
@ -77,7 +77,9 @@ Create a file ``secrets.py`` looking like following, and put it in some folder w
|
|||
.. code-block:: python
|
||||
|
||||
GCE_PARAMS = ('i...@project.googleusercontent.com', '/path/to/project.pem')
|
||||
GCE_KEYWORD_PARAMS = {'project': 'project-name'}
|
||||
GCE_KEYWORD_PARAMS = {'project': 'project_id'}
|
||||
|
||||
Ensure to enter the email address from the created services account and not the one from your main account.
|
||||
|
||||
Now the modules can be used as above, but the account information can be omitted.
|
||||
|
||||
|
@ -86,9 +88,9 @@ GCE Dynamic Inventory
|
|||
|
||||
The best way to interact with your hosts is to use the gce inventory plugin, which dynamically queries GCE and tells Ansible what nodes can be managed.
|
||||
|
||||
Note that when using the inventory script ``gce.py``, you also need to populate the ``gce.ini`` file that you can find in the plugins/inventory directory of the ansible checkout.
|
||||
Note that when using the inventory script ``gce.py``, you also need to populate the ``gce.ini`` file that you can find in the contrib/inventory directory of the ansible checkout.
|
||||
|
||||
To use the GCE dynamic inventory script, copy ``gce.py`` from ``plugins/inventory`` into your inventory directory and make it executable. You can specify credentials for ``gce.py`` using the ``GCE_INI_PATH`` environment variable -- the default is to look for gce.ini in the same directory as the inventory script.
|
||||
To use the GCE dynamic inventory script, copy ``gce.py`` from ``contrib/inventory`` into your inventory directory and make it executable. You can specify credentials for ``gce.py`` using the ``GCE_INI_PATH`` environment variable -- the default is to look for gce.ini in the same directory as the inventory script.
|
||||
|
||||
Let's see if inventory is working:
|
||||
|
||||
|
@ -109,7 +111,7 @@ Now let's see if we can use the inventory script to talk to Google.
|
|||
"x.x.x.x"
|
||||
],
|
||||
|
||||
As with all dynamic inventory plugins in Ansible, you can configure the inventory path in ansible.cfg. The recommended way to use the inventory is to create an ``inventory`` directory, and place both the ``gce.py`` script and a file containing ``localhost`` in it. This can allow for cloud inventory to be used alongside local inventory (such as a physical datacenter) or machines running in different providers.
|
||||
As with all dynamic inventory scripts in Ansible, you can configure the inventory path in ansible.cfg. The recommended way to use the inventory is to create an ``inventory`` directory, and place both the ``gce.py`` script and a file containing ``localhost`` in it. This can allow for cloud inventory to be used alongside local inventory (such as a physical datacenter) or machines running in different providers.
|
||||
|
||||
Executing ``ansible`` or ``ansible-playbook`` and specifying the ``inventory`` directory instead of an individual file will cause ansible to evaluate each file in that directory for inventory.
|
||||
|
||||
|
@ -133,7 +135,7 @@ For the following use case, let's use this small shell script as a wrapper.
|
|||
|
||||
.. code-block:: bash
|
||||
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
PLAYBOOK="$1"
|
||||
|
||||
if [[ -z $PLAYBOOK ]]; then
|
||||
|
|
|
@ -6,7 +6,7 @@ Rackspace Cloud Guide
|
|||
Introduction
|
||||
````````````
|
||||
|
||||
.. note:: This section of the documentation is under construction. We are in the process of adding more examples about the Rackspace modules and how they work together. Once complete, there will also be examples for Rackspace Cloud in `ansible-examples <http://github.com/ansible/ansible-examples/>`_.
|
||||
.. note:: This section of the documentation is under construction. We are in the process of adding more examples about the Rackspace modules and how they work together. Once complete, there will also be examples for Rackspace Cloud in `ansible-examples <https://github.com/ansible/ansible-examples/>`_.
|
||||
|
||||
Ansible contains a number of core modules for interacting with Rackspace Cloud.
|
||||
|
||||
|
@ -131,7 +131,7 @@ The rax module returns data about the nodes it creates, like IP addresses, hostn
|
|||
hostname: "{{ item.name }}"
|
||||
ansible_ssh_host: "{{ item.rax_accessipv4 }}"
|
||||
ansible_ssh_pass: "{{ item.rax_adminpass }}"
|
||||
groupname: raxhosts
|
||||
groups: raxhosts
|
||||
with_items: rax.success
|
||||
when: rax.action == 'create'
|
||||
|
||||
|
@ -163,7 +163,7 @@ In Ansible it is quite possible to use multiple dynamic inventory plugins along
|
|||
rax.py
|
||||
++++++
|
||||
|
||||
To use the rackspace dynamic inventory script, copy ``rax.py`` into your inventory directory and make it executable. You can specify a credentails file for ``rax.py`` utilizing the ``RAX_CREDS_FILE`` environment variable.
|
||||
To use the rackspace dynamic inventory script, copy ``rax.py`` into your inventory directory and make it executable. You can specify a credentials file for ``rax.py`` utilizing the ``RAX_CREDS_FILE`` environment variable.
|
||||
|
||||
.. note:: Dynamic inventory scripts (like ``rax.py``) are saved in ``/usr/share/ansible/inventory`` if Ansible has been installed globally. If installed to a virtualenv, the inventory scripts are installed to ``$VIRTUALENV/share/inventory``.
|
||||
|
||||
|
@ -519,7 +519,7 @@ Build a complete webserver environment with servers, custom networks and load ba
|
|||
ansible_ssh_host: "{{ item.rax_accessipv4 }}"
|
||||
ansible_ssh_pass: "{{ item.rax_adminpass }}"
|
||||
ansible_ssh_user: root
|
||||
groupname: web
|
||||
groups: web
|
||||
with_items: rax.success
|
||||
when: rax.action == 'create'
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue