Merge branch 'devel' into unevaluated-vars

This commit is contained in:
Lorin Hochstein 2013-05-30 16:27:48 -04:00
commit 8d919d6c97
23 changed files with 664 additions and 51 deletions

View file

@ -26,22 +26,20 @@ Core Features:
Modules added:
* rax: module for creating instances in the rackspace cloud (uses pyrax)
* npm: node.js package management
* postgresql_priv: manages postgresql priveledges
* set_fact: sets a variable, which can be the result of a template evaluation
* hipchat: send notification events to hipchat
* ec2_elb: add and remove machines from ec2 elastic load balancers
* flowdock: send messages to flowdock during playbook runs
* pkgng: next-gen package manager for FreeBSD
* bigip_pool: load balancing with F5s
* newrelic_deployment: notifies newrelic of new deployments
* campfire: send messages to campfire during playbook runs
* mqtt: send messages to the Mosquitto message bus
* irc: send messages to IRC channels
* filesystem - a wrapper around mkfs
* jabber: send jabber chat messages
* osx_say: make OS X say things out loud
* cloud: rax: module for creating instances in the rackspace cloud (uses pyrax)
* packages: npm: node.js package management
* packages: pkgng: next-gen package manager for FreeBSD
* database: postgresql_priv: manages postgresql priveledges
* networking: bigip_pool: load balancing with F5s
* networking: ec2_elb: add and remove machines from ec2 elastic load balancers
* notification: hipchat: send notification events to hipchat
* notification: flowdock: send messages to flowdock during playbook runs
* notification: campfire: send messages to campfire during playbook runs
* notification: mqtt: send messages to the Mosquitto message bus
* notification: irc: send messages to IRC channels
* notification: filesystem - a wrapper around mkfs
* notification: jabber: send jabber chat messages
* notification: osx_say: make OS X say things out loud
* openstack: keystone_user
* openstack: glance_image
* openstack: nova_compute
@ -53,7 +51,12 @@ Modules added:
* openstack: quantum_router_gateway
* openstack: quantum_router_interface
* openstack: quantum_subnet
* airbrake_deployment - notify airbrake of new deployments
* monitoring: newrelic_deployment: notifies newrelic of new deployments
* monitoring: airbrake_deployment - notify airbrake of new deployments
* monitoring: pingdom
* monitoring: pagerduty
* monitoring: monit
* utility: set_fact: sets a variable, which can be the result of a template evaluation
Modules removed
@ -119,7 +122,8 @@ the variable is still registered for the host, with the attribute skipped: True.
* pip works better when sudoing from unpriveledged users
* fix for user creation with groups specification reporting 'changed' incorrectly in some cases
* fix for some unicode encoding errors in outputing some data in verbose mode
*
* improved FreeBSD, NetBSD and Solaris facts
* debug module always outputs data without having to specify -v
1.1 "Mean Street" -- 4/2/2013

View file

@ -306,7 +306,8 @@ To see the complete list of variables available for an instance, run the script
Example: OpenStack Inventory Script
```````````````````````````````````
Though not detailed here in as much depth as the EC2 module, there's also a OpenStack Nova external inventory source in the plugins directory. See the inline comments in the module source for how to use it.
Though not detailed here in as much depth as the EC2 module, there's also a OpenStack Compute external inventory source in the plugins directory. It requires the Grizzly release of OpenStack or
later. See the inline comments in the module source for how to use it.
Callback Plugins
----------------

View file

@ -52,6 +52,7 @@ The top level of the directory would contain files and directories like so::
ntp.conf.j2 # <------- templates end in .j2
files/ #
bar.txt # <-- files for use with the copy resource
foo.sh # <-- script files for use with the script resource
webtier/ # same kind of structure as "common" was above, done for the webtier role
monitoring/ # ""

View file

@ -213,6 +213,17 @@ the remote nodes will be terminated.
Typically you'll be only be backgrounding long-running
shell commands or software upgrades only. Backgrounding the copy module does not do a background file transfer. :doc:`playbooks` also support polling, and have a simplified syntax for this.
Gathering Facts
```````````````
Facts are described in the playbooks section and represent discovered variables about a
system. These can be used to implement conditional execution of tasks but also just to get ad-hoc information about your system. You can see all facts via::
$ ansible all -m setup
Its also possible to filter this output to just export certain facts, see the "setup" module documentation for details.
Limiting Selected Hosts
```````````````````````

View file

@ -204,7 +204,19 @@ further information on using Portfiles with MacPorts.
Ubuntu and Debian
+++++++++++++++++
Ubuntu builds are available `in a PPA here <https://launchpad.net/~rquillo/+archive/ansible>`_
Ubuntu builds are available `in a PPA here <https://launchpad.net/~rquillo/+archive/ansible>`_.
In Ubuntu 13.04 (raring) its part of the backports repository:
.. code-block:: bash
$ sudo apt-get install ansible/raring-backports
In Debian testing/unstable and Ubntu 13.10+ it is available via
.. code-block:: bash
$ sudo apt-get install ansible
Debian/Ubuntu package recipes can also be built from the source checkout, run:

View file

@ -449,7 +449,7 @@ Roles
.. versionadded: 1.2
Now that you have learned about vars_files, tasks, and handlers, what is the best way to organize your playbooks?
The short answer is to use roles! Roles are automatic ways of automatically loading certain vars_files, tasks, and
The short answer is to use roles! Roles are ways of automatically loading certain vars_files, tasks, and
handlers based on a known file structure. Grouping content by roles also allows easy sharing of roles with other users.
Roles are just automation around 'include' directives as redescribed above, and really don't contain much
@ -488,6 +488,7 @@ This designates the following behaviors, for each role 'x':
- If roles/x/handlers/main.yml exists, handlers listed therein will be added to the play
- If roles/x/vars/main.yml exists, variables listed therein will be added to the play
- Any copy tasks can reference files in roles/x/files/ without having to path them relatively or absolutely
- Any script tasks can reference scripts in roles/x/files/ without having to path them relatively or absolutely
- Any template tasks can reference files in roles/x/templates/ without having to path them relatively or absolutely
If any files are not present, they are just ignored. So it's ok to not have a 'vars/' subdirectory for the role,
@ -526,6 +527,8 @@ If you want to define certain tasks to happen before AND after roles are applied
- shell: echo 'hello'
roles:
- { role: some_role }
tasks:
- shell: echo 'still busy'
post_tasks:
- shell: echo 'goodbye'

View file

@ -262,8 +262,8 @@ Conditional Execution
`````````````````````
(Note: this section covers 1.2 conditionals, if you are using a previous version, select
the previous version of the documentation. Those conditional forms continue to be operational
in 1.2, although the new mechanisms are cleaner.)
the previous version of the documentation, `Ansible 1.1 Docs <http://ansible.cc/docs/released/1.1>`_ .
Those conditional forms continue to be operational in 1.2, although the new mechanisms are cleaner.)
Sometimes you will want to skip a particular step on a particular host. This could be something
as simple as not installing a certain package if the operating system is a particular version,
@ -414,7 +414,11 @@ The yum and apt modules use with_items to execute fewer package manager transact
Note that the types of items you iterate over with 'with_items' do not have to be simple lists of strings.
If you have a list of hashes, you can reference subkeys using things like::
{{ item.subKeyName }}
- name: add several users
action: user name={{ item.name }} state=present groups={{ item.groups }}
with_items:
- { name: 'testuser1', groups: 'wheel' }
- { name: 'testuser2', groups: 'root' }
Lookup Plugins - Accessing Outside Data
```````````````````````````````````````

View file

@ -85,10 +85,10 @@ def boilerplate_module(modfile, args):
if included_boilerplate:
module_data = module_data.replace(module_common.REPLACER, module_common.MODULE_COMMON)
encoded_args = "\"\"\"%s\"\"\"" % args.replace("\"","\\\"")
encoded_args = repr(str(args))
module_data = module_data.replace(module_common.REPLACER_ARGS, encoded_args)
encoded_lang = "\"\"\"%s\"\"\"" % C.DEFAULT_MODULE_LANG
empty_complex = "\"\"\"%s\"\"\"" % "{}"
encoded_lang = repr(C.DEFAULT_MODULE_LANG)
empty_complex = repr("{}")
module_data = module_data.replace(module_common.REPLACER_LANG, encoded_lang)
module_data = module_data.replace('syslog.LOG_USER', "syslog.%s" % C.DEFAULT_SYSLOG_FACILITY)
module_data = module_data.replace(module_common.REPLACER_COMPLEX, empty_complex)

View file

@ -65,7 +65,7 @@ class InventoryParser(object):
for line in self.lines:
if line.startswith("["):
active_group_name = line.replace("[","").replace("]","").strip()
active_group_name = line.split("#")[0].replace("[","").replace("]","").strip()
if line.find(":vars") != -1 or line.find(":children") != -1:
active_group_name = active_group_name.rsplit(":", 1)[0]
if active_group_name not in self.groups:
@ -78,7 +78,7 @@ class InventoryParser(object):
elif line.startswith("#") or line == '':
pass
elif active_group_name:
tokens = shlex.split(line)
tokens = shlex.split(line.split("#")[0])
if len(tokens) == 0:
continue
hostname = tokens[0]

View file

@ -710,9 +710,11 @@ class Runner(object):
module_style = 'non_native_want_json'
complex_args_json = utils.jsonify(complex_args)
encoded_args = "\"\"\"%s\"\"\"" % module_args.replace("\"","\\\"")
encoded_lang = "\"\"\"%s\"\"\"" % C.DEFAULT_MODULE_LANG
encoded_complex = "\"\"\"%s\"\"\"" % complex_args_json.replace("\\", "\\\\")
# We force conversion of module_args to str because module_common calls shlex.split,
# a standard library function that incorrectly handles Unicode input before Python 2.7.3.
encoded_args = repr(str(module_args))
encoded_lang = repr(C.DEFAULT_MODULE_LANG)
encoded_complex = repr(complex_args_json)
module_data = module_data.replace(module_common.REPLACER, module_common.MODULE_COMMON)
module_data = module_data.replace(module_common.REPLACER_ARGS, encoded_args)

View file

@ -41,7 +41,10 @@ class ActionModule(object):
# FIXME: error handling
args = " ".join(tokens[1:])
source = template.template(self.runner.basedir, source, inject)
source = utils.path_dwim(self.runner.basedir, source)
if '_original_file' in inject:
source = utils.path_dwim_relative(inject['_original_file'], 'files', source, self.runner.basedir)
else:
source = utils.path_dwim(self.runner.basedir, source)
# transfer the file to a remote tmp location
source = source.replace('\x00','') # why does this happen here?

View file

@ -49,6 +49,7 @@ def get_docstring(filename, verbose=False):
if 'EXAMPLES' in (t.id for t in child.targets):
plainexamples = child.value.s[1:] # Skip first empty line
except:
traceback.print_exc() # temp
if verbose == True:
traceback.print_exc()
print "unable to parse %s" % filename

View file

@ -22,7 +22,7 @@ try:
from keystoneclient.v2_0 import client as ksclient
import time
except ImportError:
print("failed=True msg='glanceclient,novaclient and keystone client are required'")
print "failed=True msg='glanceclient,novaclient and keystone client are required'"
DOCUMENTATION = '''
---
@ -130,9 +130,7 @@ def _get_server_state(module, nova):
return server_info, server
def _get_port_id(quantum, module, instance_id):
kwargs = {
device_id': instance_id,
}
kwargs = dict(device_id = instance_id)
try:
ports = quantum.list_ports(**kwargs)
except Exception as e:

View file

@ -95,7 +95,7 @@ def main():
module.fail_json(rc=256, msg="no command given")
if chdir:
os.chdir(os.path.expanduser(chdir))
os.chdir(chdir)
if creates:
# do not run the command if the line contains creates=filename

View file

@ -126,7 +126,7 @@ EXAMPLES = r"""
lineinfile: dest=/etc/sudoers state=present regexp='^%wheel' line='%wheel ALL=(ALL) NOPASSWD: ALL'
lineinfile: dest=/opt/jboss-as/bin/standalone.conf regexp='^(.*)Xms(\d+)m(.*)$' line='\\1Xms${xms}m\\3' backrefs=yes
lineinfile: dest=/opt/jboss-as/bin/standalone.conf regexp='^(.*)Xms(\d+)m(.*)$' line='\1Xms${xms}m\3' backrefs=yes
"""
def write_changes(module,lines,dest):

146
library/monitoring/monit Normal file
View file

@ -0,0 +1,146 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2013, Darryl Stoflet <stoflet@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/>.
#
DOCUMENTATION = '''
---
module: monit
short_description: Manage the state of a program monitored via Monit
description:
- Manage the state of a program monitored via I(Monit)
version_added: "1.2"
options:
name:
description:
- The name of the I(monit) program/process to manage
required: true
default: null
state:
description:
- The state of service
required: true
default: null
choices: [ "present", "started", "stopped", "restarted", "monitored", "unmonitored", "reloaded" ]
examples:
- code: "monit: name=httpd state=started"
description: Manage the state of program I(httpd) to be in I(started) state.
requirements: [ ]
author: Darryl Stoflet
'''
def main():
arg_spec = dict(
name=dict(required=True),
state=dict(required=True, choices=['present', 'started', 'restarted', 'stopped', 'monitored', 'unmonitored', 'reloaded'])
)
module = AnsibleModule(argument_spec=arg_spec, supports_check_mode=True)
name = module.params['name']
state = module.params['state']
MONIT = module.get_bin_path('monit', True)
if state == 'reloaded':
if module.check_mode:
module.exit_json(changed=True)
rc, out, err = module.run_command('%s reload' % MONIT)
module.exit_json(changed=True, name=name, state=state)
rc, out, err = module.run_command('%s summary | grep "Process \'%s\'"' % (MONIT, name))
present = name in out
if not present and not state == 'present':
module.fail_json(msg='%s process not presently configured with monit' % name, name=name, state=state)
if state == 'present':
if not present:
if module.check_mode:
module.exit_json(changed=True)
module.run_command('%s reload' % MONIT, check_rc=True)
rc, out, err = module.run_command('%s summary | grep %s' % (MONIT, name))
if name in out:
module.exit_json(changed=True, name=name, state=state)
else:
module.fail_json(msg=out, name=name, state=state)
module.exit_json(changed=False, name=name, state=state)
rc, out, err = module.run_command('%s summary | grep %s' % (MONIT, name))
running = 'Running' in out
if running and (state == 'started' or state == 'monitored'):
module.exit_json(changed=False, name=name, state=state)
if running and state == 'monitored':
module.exit_json(changed=False, name=name, state=state)
if running and state == 'stopped':
if module.check_mode:
module.exit_json(changed=True)
module.run_command('%s stop %s' % (MONIT, name))
rc, out, err = module.run_command('%s summary | grep %s' % (MONIT, name))
if 'Not monitored' in out or 'stop pending' in out:
module.exit_json(changed=True, name=name, state=state)
module.fail_json(msg=out)
if running and state == 'unmonitored':
if module.check_mode:
module.exit_json(changed=True)
module.run_command('%s unmonitor %s' % (MONIT, name))
rc, out, err = module.run_command('%s summary | grep %s' % (MONIT, name))
if 'Not monitored' in out:
module.exit_json(changed=True, name=name, state=state)
module.fail_json(msg=out)
elif state == 'restarted':
if module.check_mode:
module.exit_json(changed=True)
module.run_command('%s stop %s' % (MONIT, name))
rc, out, err = module.run_command('%s start %s' % (MONIT, name))
if 'Initializing' in out:
module.exit_json(changed=True, name=name, state=state)
module.fail_json(msg=out)
elif not running and state == 'started':
if module.check_mode:
module.exit_json(changed=True)
module.run_command('%s start %s' % (MONIT, name))
rc, out, err = module.run_command('%s summary | grep %s' % (MONIT, name))
if 'Initializing' in out or 'start pending' in out:
module.exit_json(changed=True, name=name, state=state)
module.fail_json(msg=out)
elif not running and state == 'monitored':
if module.check_mode:
module.exit_json(changed=True)
module.run_command('%s monitor %s' % (MONIT, name))
rc, out, err = module.run_command('%s summary | grep %s' % (MONIT, name))
if 'Initializing' in out or 'start pending' in out:
module.exit_json(changed=True, name=name, state=state)
module.fail_json(msg=out)
module.exit_json(changed=False, name=name, state=state)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

View file

@ -0,0 +1,154 @@
#!/usr/bin/python
DOCUMENTATION = '''
module: pagerduty
short_description: Create PagerDuty maintenance windows
description:
- This module will let you create PagerDuty maintenance windows
version_added: "1.2"
author: Justin Johns
requirements:
- PagerDuty API access
options:
state:
description:
- Create a maintenance window or get a list of ongoing windows.
required: true
default: null
choices: [ "running", "started", "ongoing" ]
aliases: []
name:
description:
- PagerDuty unique subdomain.
required: true
default: null
choices: []
aliases: []
user:
description:
- PagerDuty user ID.
required: true
default: null
choices: []
aliases: []
passwd:
description:
- PagerDuty user password.
required: true
default: null
choices: []
aliases: []
service:
description:
- PagerDuty service ID.
required: false
default: null
choices: []
aliases: []
hours:
description:
- Length of maintenance window in hours.
required: false
default: 1
choices: []
aliases: []
desc:
description:
- Short description of maintenance window.
required: false
default: Created by Ansible
choices: []
aliases: []
notes:
- This module does not yet have support to end maintenance windows.
'''
EXAMPLES='''
# List ongoing maintenance windows.
pagerduty: name=companyabc user=example@example.com passwd=password123 state=ongoing
# Create a 1 hour maintenance window for service FOO123.
pagerduty: name=companyabc user=example@example.com passwd=password123 state=running service=FOO123"
# Create a 4 hour maintenance window for service FOO123 with the description "deployment".
pagerduty: name=companyabc user=example@example.com passwd=password123 state=running service=FOO123 hours=4 desc=deployment"
'''
import json
import datetime
import urllib2
import base64
def ongoing(name, user, passwd):
url = "https://" + name + ".pagerduty.com/api/v1/maintenance_windows/ongoing"
auth = base64.encodestring('%s:%s' % (user, passwd)).replace('\n', '')
req = urllib2.Request(url)
req.add_header("Authorization", "Basic %s" % auth)
res = urllib2.urlopen(req)
out = res.read()
return False, out
def create(name, user, passwd, service, hours, desc):
now = datetime.datetime.utcnow()
later = now + datetime.timedelta(hours=int(hours))
start = now.strftime("%Y-%m-%dT%H:%M:%SZ")
end = later.strftime("%Y-%m-%dT%H:%M:%SZ")
url = "https://" + name + ".pagerduty.com/api/v1/maintenance_windows"
auth = base64.encodestring('%s:%s' % (user, passwd)).replace('\n', '')
data = json.dumps({'maintenance_window': {'start_time': start, 'end_time': end, 'description': desc, 'service_ids': [service]}})
req = urllib2.Request(url, data)
req.add_header("Authorization", "Basic %s" % auth)
req.add_header('Content-Type', 'application/json')
res = urllib2.urlopen(req)
out = res.read()
return False, out
def main():
module = AnsibleModule(
argument_spec=dict(
state=dict(required=True, choices=['running', 'started', 'ongoing']),
name=dict(required=True),
user=dict(required=True),
passwd=dict(required=True),
service=dict(required=False),
hours=dict(default='1', required=False),
desc=dict(default='Created by Ansible', required=False)
)
)
state = module.params['state']
name = module.params['name']
user = module.params['user']
passwd = module.params['passwd']
service = module.params['service']
hours = module.params['hours']
desc = module.params['desc']
if state == "running" or state == "started":
if not service:
module.fail_json(msg="service not specified")
(rc, out) = create(name, user, passwd, service, hours, desc)
if state == "ongoing":
(rc, out) = ongoing(name, user, passwd)
if rc != 0:
module.fail_json(msg="failed", result=out)
module.exit_json(msg="success", result=out)
# include magic from lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

119
library/monitoring/pingdom Normal file
View file

@ -0,0 +1,119 @@
#!/usr/bin/python
DOCUMENTATION = '''
module: pingdom
short_description: Pause/unpause Pingdom alerts
description:
- This module will let you pause/unpause Pingdom alerts
version_added: "1.2"
author: Justin Johns
requirements:
- "pingdom python library"
options:
state:
description:
- Define whether or not the check should be running or paused.
required: true
default: null
choices: [ "running", "paused" ]
aliases: []
checkid:
description:
- Pingdom ID of the check.
required: true
default: null
choices: []
aliases: []
uid:
description:
- Pingdom user ID.
required: true
default: null
choices: []
aliases: []
passwd:
description:
- Pingdom user password.
required: true
default: null
choices: []
aliases: []
key:
description:
- Pingdom API key.
required: true
default: null
choices: []
aliases: []
notes:
- This module does not yet have support to add/remove checks.
'''
EXAMPLES = '''
# Pause the check with the ID of 12345.
pingdom: uid=example@example.com passwd=password123 key=apipassword123 checkid=12345 state=paused
# Unpause the check with the ID of 12345.
pingdom: uid=example@example.com passwd=password123 key=apipassword123 checkid=12345 state=running
'''
import pingdom
def pause(checkid, uid, passwd, key):
c = pingdom.PingdomConnection(uid, passwd, key)
c.modify_check(checkid, paused=True)
check = c.get_check(checkid)
name = check.name
result = check.status
#if result != "paused": # api output buggy - accept raw exception for now
# return (True, name, result)
return (False, name, result)
def unpause(checkid, uid, passwd, key):
c = pingdom.PingdomConnection(uid, passwd, key)
c.modify_check(checkid, paused=False)
check = c.get_check(checkid)
name = check.name
result = check.status
#if result != "up": # api output buggy - accept raw exception for now
# return (True, name, result)
return (False, name, result)
def main():
module = AnsibleModule(
argument_spec=dict(
state=dict(required=True, choices=['running', 'paused', 'started', 'stopped']),
checkid=dict(required=True),
uid=dict(required=True),
passwd=dict(required=True),
key=dict(required=True)
)
)
checkid = module.params['checkid']
state = module.params['state']
uid = module.params['uid']
passwd = module.params['passwd']
key = module.params['key']
if (state == "paused" or state == "stopped"):
(rc, name, result) = pause(checkid, uid, passwd, key)
if (state == "running" or state == "started"):
(rc, name, result) = unpause(checkid, uid, passwd, key)
if rc != 0:
module.fail_json(checkid=checkid, name=name, status=result)
module.exit_json(checkid=checkid, name=name, status=result)
# include magic from lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()

View file

@ -88,11 +88,12 @@ def main():
if module.check_mode:
changed = True
else:
mkfs = module.get_bin_path('mkfs', required=True)
cmd = None
if opts is None:
cmd = "mkfs -t %s '%s'"%(fstype, dev)
cmd = "%s -t %s '%s'" % (mkfs, fstype, dev)
else:
cmd = "mkfs -t %s %s '%s'"%(fstype, opts, dev)
cmd = "%s -t %s %s '%s'" % (mkfs, fstype, opts, dev)
rc,_,err = module.run_command(cmd)
if rc == 0:
changed = True

View file

@ -121,6 +121,7 @@ class Facts(object):
{ 'path' : '/opt/local/bin/pkgin', 'name' : 'pkgin' },
{ 'path' : '/opt/local/bin/port', 'name' : 'macports' },
{ 'path' : '/sbin/apk', 'name' : 'apk' },
{ 'path' : '/usr/sbin/pkg', 'name' : 'pkgng' },
]
def __init__(self):
@ -175,7 +176,8 @@ class Facts(object):
SLED = 'Suse', OpenSuSE = 'Suse', SuSE = 'Suse', Gentoo = 'Gentoo',
Archlinux = 'Archlinux', Mandriva = 'Mandrake', Mandrake = 'Mandrake',
Solaris = 'Solaris', Nexenta = 'Solaris', OmniOS = 'Solaris', OpenIndiana = 'Solaris',
SmartOS = 'Solaris', AIX = 'AIX', Alpine = 'Alpine', MacOSX = 'Darwin'
SmartOS = 'Solaris', AIX = 'AIX', Alpine = 'Alpine', MacOSX = 'Darwin',
FreeBSD = 'FreeBSD'
)
if self.facts['system'] == 'AIX':
@ -189,6 +191,10 @@ class Facts(object):
rc, out, err = module.run_command("/usr/bin/sw_vers -productVersion")
data = out.split()[-1]
self.facts['distribution_version'] = data
elif self.facts['system'] == 'FreeBSD':
self.facts['distribution'] = 'FreeBSD'
self.facts['distribution_release'] = platform.release()
self.facts['distribution_version'] = platform.version()
else:
dist = platform.dist()
self.facts['distribution'] = dist[0].capitalize() or 'NA'
@ -245,12 +251,15 @@ class Facts(object):
def get_public_ssh_host_keys(self):
dsa_filename = '/etc/ssh/ssh_host_dsa_key.pub'
rsa_filename = '/etc/ssh/ssh_host_rsa_key.pub'
ecdsa_filename = '/etc/ssh/ssh_host_ecdsa_key.pub'
if self.facts['system'] == 'Darwin':
dsa_filename = '/etc/ssh_host_dsa_key.pub'
rsa_filename = '/etc/ssh_host_rsa_key.pub'
ecdsa_filename = '/etc/ssh_host_ecdsa_key.pub'
dsa = get_file_content(dsa_filename)
rsa = get_file_content(rsa_filename)
ecdsa = get_file_content(ecdsa_filename)
if dsa is None:
dsa = 'NA'
else:
@ -259,6 +268,10 @@ class Facts(object):
rsa = 'NA'
else:
self.facts['ssh_host_key_rsa_public'] = rsa.split()[1]
if ecdsa is None:
ecdsa = 'NA'
else:
self.facts['ssh_host_key_ecdsa_public'] = ecdsa.split()[1]
def get_pkg_mgr_facts(self):
self.facts['pkg_mgr'] = 'unknown'
@ -1620,7 +1633,7 @@ class LinuxVirtual(Virtual):
return
# Beware that we can have both kvm and virtualbox running on a single system
if os.path.exists("/proc/modules"):
if os.path.exists("/proc/modules") and os.access('/proc/modules', os.R_OK):
modules = []
for line in open("/proc/modules").readlines():
data = line.split(" ", 1)

132
plugins/inventory/vagrant.py Executable file
View file

@ -0,0 +1,132 @@
#!/usr/bin/env python
"""
Vagrant external inventory script. Automatically finds the IP of the booted vagrant vm(s), and
returns it under the host group 'vagrant'
Example Vagrant configuration using this script:
config.vm.provision :ansible do |ansible|
ansible.playbook = "./provision/your_playbook.yml"
ansible.inventory_file = "./provision/inventory/vagrant.py"
ansible.verbose = true
end
"""
# Copyright (C) 2013 Mark Mandel <mark@compoundtheory.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 spacewalk.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 Vagrant servers for Ansible")
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
#
# get all the ssh configs for all boxes in an array of dictionaries.
def get_ssh_config():
configs = []
boxes = list_running_boxes()
for box in boxes:
config = get_a_ssh_config(box)
configs.append(config)
return configs
#list all the running boxes
def list_running_boxes():
output = subprocess.check_output(["vagrant", "status"]).split('\n')
boxes = []
for line in output:
matcher = re.search("([^\s]+)[\s]+running \(.+", line)
if matcher:
boxes.append(matcher.group(1))
return boxes
#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')
config = {}
for line in output:
if line.strip() != '':
matcher = re.search("( )?([a-zA-Z]+) (.*)", line)
config[matcher.group(2)] = matcher.group(3)
return config
# List out servers that vagrant has running
#------------------------------
if options.list:
ssh_config = get_ssh_config()
hosts = { 'vagrant': []}
for data in ssh_config:
hosts['vagrant'].append(data['HostName'])
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['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 out help
#------------------------------
else:
parser.print_help()
sys.exit(1)

View file

@ -477,7 +477,7 @@ class TestRunner(unittest.TestCase):
# The order of the test cases is important
# The regexp doesn't match, so the line will not be added anywhere.
testline = r'\\1: Line added by default at the end of the file.'
testline = r'\1: Line added by default at the end of the file.'
testcase = ('lineinfile', [
"dest=%s" % sample,
"regexp='^(First): '",
@ -492,7 +492,7 @@ class TestRunner(unittest.TestCase):
# insertafter with EOF
# The regexp doesn't match, so the line will not be added anywhere.
testline = r'\\1: Line added with insertafter=EOF'
testline = r'\1: Line added with insertafter=EOF'
testcase = ('lineinfile', [
"dest=%s" % sample,
"insertafter=EOF",
@ -508,7 +508,7 @@ class TestRunner(unittest.TestCase):
# with invalid insertafter regex
# The regexp doesn't match, so do nothing.
testline = r'\\1: Line added with an invalid insertafter regex'
testline = r'\1: Line added with an invalid insertafter regex'
testcase = ('lineinfile', [
"dest=%s" % sample,
"insertafter='^abcdefgh'",
@ -522,7 +522,7 @@ class TestRunner(unittest.TestCase):
# with an insertafter regex
# The regexp doesn't match, so do nothing.
testline = r'\\1: Line added with a valid insertafter regex'
testline = r'\1: Line added with a valid insertafter regex'
testcase = ('lineinfile', [
"dest=%s" % sample,
"insertafter='^receive messages to '",
@ -541,7 +541,7 @@ class TestRunner(unittest.TestCase):
target_line = 'combination of microphone, speaker, keyboard and display. It can send and'
idx = artifact.index(target_line)
testline = r'\\1 of megaphone'
testline = r'\1 of megaphone'
testline_after = 'combination of megaphone'
testcase = ('lineinfile', [
"dest=%s" % sample,
@ -558,7 +558,7 @@ class TestRunner(unittest.TestCase):
assert target_line not in artifact
# Go again, should be unchanged now.
testline = r'\\1 of megaphone'
testline = r'\1 of megaphone'
testline_after = 'combination of megaphone'
testcase = ('lineinfile', [
"dest=%s" % sample,
@ -574,11 +574,11 @@ class TestRunner(unittest.TestCase):
f = open(sample, 'a+')
f.write("1 + 1 = 3" + os.linesep)
f.close()
testline = r"2 + \\g<num> = 3"
testline = r"2 + \g<num> = 3"
testline_after = "2 + 1 = 3"
testcase = ('lineinfile', [
"dest=%s" % sample,
r"regexp='1 \\+ (?P<num>\\d) = 3'",
r"regexp='1 \+ (?P<num>\d) = 3'",
"line='%s'" % testline,
"backrefs=yes",
])

View file

@ -0,0 +1,8 @@
[major-god] # group with inline comments
zeus var_a=1 # host with inline comments
# A comment
thor
[minor-god] # group with inline comment and unbalanced quotes: ' "
morpheus # host with inline comments and unbalanced quotes: ' "
# A comment with unbalanced quotes: ' "