diff --git a/monitoring/boundary_meter b/monitoring/boundary_meter new file mode 100644 index 00000000000..71ea2b548c4 --- /dev/null +++ b/monitoring/boundary_meter @@ -0,0 +1,271 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +""" +Ansible module to add boundary meters. + +(c) 2013, curtis + +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 . +""" + +import json +import datetime +import urllib2 +import base64 +import os + +DOCUMENTATION = ''' + +module: boundary_meter +short_description: Manage boundary meters +description: + - This module manages boundary meters +version_added: "1.3" +author: curtis@serverascode.com +requirements: + - Boundary API access + - bprobe is required to send data, but not to register a meter + - Python urllib2 +options: + name: + description: + - meter name + required: true + state: + description: + - Whether to create or remove the client from boundary + required: false + default: true + choices: ["present", "absent"] + apiid: + description: + - Organizations boundary API ID + required: true + apikey: + description: + - Organizations boundary API KEY + required: true + +notes: + - This module does not yet support boundary tags. + +''' + +EXAMPLES=''' +- name: Create meter + boundary_meter: apiid=AAAAAA api_key=BBBBBB state=present name={{ inventory_hostname }}" + +- name: Delete meter + boundary_meter: apiid=AAAAAA api_key=BBBBBB state=absent name={{ inventory_hostname }}" + +''' + +try: + import urllib2 + HAS_URLLIB2 = True +except ImportError: + HAS_URLLIB2 = False + +api_host = "api.boundary.com" +config_directory = "/etc/bprobe" + +# "resource" like thing or apikey? +def auth_encode(apikey): + auth = base64.standard_b64encode(apikey) + auth.replace("\n", "") + return auth + +def build_url(name, apiid, action, meter_id=None, cert_type=None): + if action == "create": + return 'https://%s/%s/meters' % (api_host, apiid) + elif action == "search": + return "https://%s/%s/meters?name=%s" % (api_host, apiid, name) + elif action == "certificates": + return "https://%s/%s/meters/%s/%s.pem" % (api_host, apiid, meter_id, cert_type) + elif action == "tags": + return "https://%s/%s/meters/%s/tags" % (api_host, apiid, meter_id) + elif action == "delete": + return "https://%s/%s/meters/%s" % (api_host, apiid, meter_id) + +def http_request(name, apiid, apikey, action, meter_id=None, cert_type=None): + + if meter_id is None: + url = build_url(name, apiid, action) + else: + if cert_type is None: + url = build_url(name, apiid, action, meter_id) + else: + url = build_url(name, apiid, action, meter_id, cert_type) + + auth = auth_encode(apikey) + request = urllib2.Request(url) + request.add_header("Authorization", "Basic %s" % (auth)) + request.add_header("Content-Type", "application/json") + return request + +def create_meter(module, name, apiid, apikey): + + meters = search_meter(module, name, apiid, apikey) + + if len(meters) > 0: + # If the meter already exists, do nothing + module.exit_json(status="Meter " + name + " already exists",changed=False) + else: + # If it doesn't exist, create it + request = http_request(name, apiid, apikey, action="create") + # A create request seems to need a json body with the name of the meter in it + body = '{"name":"' + name + '"}' + request.add_data(body) + + try: + result = urllib2.urlopen(request) + except urllib2.URLError, e: + module.fail_json(msg="Failed to connect to api host to create meter") + + # If the config directory doesn't exist, create it + if not os.path.exists(config_directory): + try: + os.makedirs(config_directory) + except: + module.fail_json("Could not create " + config_directory) + + + # Download both cert files from the api host + types = ['key', 'cert'] + for cert_type in types: + try: + # If we can't open the file it's not there, so we should download it + cert_file = open('%s/%s.pem' % (config_directory,cert_type)) + except IOError: + # Now download the file... + rc = download_request(module, name, apiid, apikey, cert_type) + if rc == False: + module.fail_json("Download request for " + cert_type + ".pem failed") + + return 0, "Meter " + name + " created" + +def search_meter(module, name, apiid, apikey): + + request = http_request(name, apiid, apikey, action="search") + + try: + result = urllib2.urlopen(request) + except urllib2.URLError, e: + module.fail_json("Failed to connect to api host to search for meter") + + # Return meters + return json.loads(result.read()) + +def get_meter_id(module, name, apiid, apikey): + # In order to delete the meter we need its id + meters = search_meter(module, name, apiid, apikey) + + if len(meters) > 0: + return meters[0]['id'] + else: + return None + +def delete_meter(module, name, apiid, apikey): + + meter_id = get_meter_id(module, name, apiid, apikey) + + if meter_id is None: + return 1, "Meter does not exist, so can't delete it" + else: + action = "delete" + request = http_request(name, apiid, apikey, action, meter_id) + # See http://stackoverflow.com/questions/4511598/how-to-make-http-delete-method-using-urllib2 + # urllib2 only does GET or POST I believe, but here we need delete + request.get_method = lambda: 'DELETE' + + try: + result = urllib2.urlopen(request) + except urllib2.URLError, e: + module.fail_json("Failed to connect to api host to delete meter") + + # Each new meter gets a new key.pem and ca.pem file, so they should be deleted + types = ['cert', 'key'] + for cert_type in types: + try: + cert_file = '%s/%s.pem' % (config_directory,cert_type) + os.remove(cert_file) + except OSError, e: ## if failed, report it back to the user ## + module.fail_json("Failed to remove " + cert_type + ".pem file") + + return 0, "Meter " + name + " deleted" + +def download_request(module, name, apiid, apikey, cert_type): + + meter_id = get_meter_id(module, name, apiid, apikey) + + if meter_id is not None: + action = "certificates" + request = http_request(name, apiid, apikey, action, meter_id, cert_type) + + try: + result = urllib2.urlopen(request) + except urllib2.URLError, e: + module.fail_json("Failed to connect to api host to download certificate") + + if result: + try: + cert_file_path = '%s/%s.pem' % (config_directory,cert_type) + body = result.read() + cert_file = open(cert_file_path, 'w') + cert_file.write(body) + cert_file.close + os.chmod(cert_file_path, 0o600) + except: + module.fail_json("Could not write to certificate file") + + return True + else: + module.fail_json("Could not get meter id") + +def main(): + + if not HAS_URLLIB2: + module.fail_json(msg="urllib2 is not installed") + + module = AnsibleModule( + argument_spec=dict( + state=dict(required=True, choices=['present', 'absent']), + name=dict(required=False), + apikey=dict(required=True), + apiid=dict(required=True), + ) + ) + + state = module.params['state'] + name= module.params['name'] + apikey = module.params['api_key'] + apiid = module.params['api_id'] + + if state == "present": + (rc, result) = create_meter(module, name, apiid, apikey) + + if state == "absent": + (rc, result) = delete_meter(module, name, apiid, apikey) + + if rc != 0: + module.fail_json(msg=result) + + module.exit_json(status=result,changed=True) + +# include magic from lib/ansible/module_common.py +#<> +main() \ No newline at end of file