diff --git a/CHANGELOG.md b/CHANGELOG.md index acae7442156..162fda7fcb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/docsite/latest/rst/api.rst b/docsite/latest/rst/api.rst index f037b1bc867..3c04c8902ec 100644 --- a/docsite/latest/rst/api.rst +++ b/docsite/latest/rst/api.rst @@ -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 ---------------- diff --git a/docsite/latest/rst/bestpractices.rst b/docsite/latest/rst/bestpractices.rst index 1a1d2ea137f..c0afa4488fd 100644 --- a/docsite/latest/rst/bestpractices.rst +++ b/docsite/latest/rst/bestpractices.rst @@ -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/ # "" diff --git a/docsite/latest/rst/examples.rst b/docsite/latest/rst/examples.rst index 93ffe624c5b..4c8abf7d2e7 100644 --- a/docsite/latest/rst/examples.rst +++ b/docsite/latest/rst/examples.rst @@ -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 ``````````````````````` diff --git a/docsite/latest/rst/gettingstarted.rst b/docsite/latest/rst/gettingstarted.rst index 19ab7e85672..99d30e5a4ee 100644 --- a/docsite/latest/rst/gettingstarted.rst +++ b/docsite/latest/rst/gettingstarted.rst @@ -204,7 +204,19 @@ further information on using Portfiles with MacPorts. Ubuntu and Debian +++++++++++++++++ -Ubuntu builds are available `in a PPA here `_ +Ubuntu builds are available `in a PPA here `_. + +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: diff --git a/docsite/latest/rst/playbooks.rst b/docsite/latest/rst/playbooks.rst index 80c49569f8f..9cbfa49e51c 100644 --- a/docsite/latest/rst/playbooks.rst +++ b/docsite/latest/rst/playbooks.rst @@ -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' diff --git a/docsite/latest/rst/playbooks2.rst b/docsite/latest/rst/playbooks2.rst index 5309e43eef9..dfa2c85c44a 100644 --- a/docsite/latest/rst/playbooks2.rst +++ b/docsite/latest/rst/playbooks2.rst @@ -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 `_ . +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 ``````````````````````````````````````` diff --git a/hacking/test-module b/hacking/test-module index 85d3c16a967..ef75eb3d911 100755 --- a/hacking/test-module +++ b/hacking/test-module @@ -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) diff --git a/lib/ansible/inventory/ini.py b/lib/ansible/inventory/ini.py index 78d5743fa3a..314f2c7cd96 100644 --- a/lib/ansible/inventory/ini.py +++ b/lib/ansible/inventory/ini.py @@ -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] diff --git a/lib/ansible/runner/__init__.py b/lib/ansible/runner/__init__.py index d373d6f9983..e9c2d1e3416 100644 --- a/lib/ansible/runner/__init__.py +++ b/lib/ansible/runner/__init__.py @@ -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) diff --git a/lib/ansible/runner/action_plugins/script.py b/lib/ansible/runner/action_plugins/script.py index b86d9c3f1d1..84c0ab015bf 100644 --- a/lib/ansible/runner/action_plugins/script.py +++ b/lib/ansible/runner/action_plugins/script.py @@ -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? diff --git a/lib/ansible/utils/module_docs.py b/lib/ansible/utils/module_docs.py index 57d914cfbe4..afa4c6536ed 100755 --- a/lib/ansible/utils/module_docs.py +++ b/lib/ansible/utils/module_docs.py @@ -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 diff --git a/library/cloud/quantum_floating_ip_associate b/library/cloud/quantum_floating_ip_associate index a20d9974edf..d39155aca91 100644 --- a/library/cloud/quantum_floating_ip_associate +++ b/library/cloud/quantum_floating_ip_associate @@ -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: diff --git a/library/commands/command b/library/commands/command index e996c0c4a7f..ad6ed6c171e 100644 --- a/library/commands/command +++ b/library/commands/command @@ -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 diff --git a/library/files/lineinfile b/library/files/lineinfile index e23748a0e22..7aac8e81c35 100644 --- a/library/files/lineinfile +++ b/library/files/lineinfile @@ -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): diff --git a/library/monitoring/monit b/library/monitoring/monit new file mode 100644 index 00000000000..de47bc0c264 --- /dev/null +++ b/library/monitoring/monit @@ -0,0 +1,146 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2013, Darryl Stoflet +# +# 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 . +# + +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 +#<> + +main() diff --git a/library/monitoring/pagerduty b/library/monitoring/pagerduty new file mode 100644 index 00000000000..200d45b0491 --- /dev/null +++ b/library/monitoring/pagerduty @@ -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 +#<> +main() diff --git a/library/monitoring/pingdom b/library/monitoring/pingdom new file mode 100644 index 00000000000..5e566e6dc4d --- /dev/null +++ b/library/monitoring/pingdom @@ -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 +#<> +main() diff --git a/library/system/filesystem b/library/system/filesystem index 5a78c1c3945..b958f100e5c 100755 --- a/library/system/filesystem +++ b/library/system/filesystem @@ -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 diff --git a/library/system/setup b/library/system/setup index 15a593b2666..f8f7926e840 100644 --- a/library/system/setup +++ b/library/system/setup @@ -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) diff --git a/plugins/inventory/vagrant.py b/plugins/inventory/vagrant.py new file mode 100755 index 00000000000..ea59a7bc023 --- /dev/null +++ b/plugins/inventory/vagrant.py @@ -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 +# +# 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 . + +# +# 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 ") +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) \ No newline at end of file diff --git a/test/TestRunner.py b/test/TestRunner.py index d833a01be0c..b6a74f2b4f0 100644 --- a/test/TestRunner.py +++ b/test/TestRunner.py @@ -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 = 3" + testline = r"2 + \g = 3" testline_after = "2 + 1 = 3" testcase = ('lineinfile', [ "dest=%s" % sample, - r"regexp='1 \\+ (?P\\d) = 3'", + r"regexp='1 \+ (?P\d) = 3'", "line='%s'" % testline, "backrefs=yes", ]) diff --git a/test/inventory_dir/3comments b/test/inventory_dir/3comments new file mode 100644 index 00000000000..02ff6ec000b --- /dev/null +++ b/test/inventory_dir/3comments @@ -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: ' "