From 56bfae361f3dffc47619f1b63aae4d2297b2f546 Mon Sep 17 00:00:00 2001 From: Curtis Date: Wed, 19 Jun 2013 16:01:47 -0600 Subject: [PATCH] initial commit of boundary_meter module --- library/monitoring/boundary_meter | 271 ++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) create mode 100644 library/monitoring/boundary_meter diff --git a/library/monitoring/boundary_meter b/library/monitoring/boundary_meter new file mode 100644 index 0000000000..7606b01dee --- /dev/null +++ b/library/monitoring/boundary_meter @@ -0,0 +1,271 @@ +#!/usr/bin/python + +# -*- coding: utf-8 -*- + +""" +Ansible module to add boundary meters. + +(c) 2013, curtis + +* Module sponsored by Cybera - a non-profit organization providing +high-capacity networking and computing solutions to the province +of Alberta. +* Please note much of this converted from the boundary puppet module! + +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 + +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 + - Boundary client is needed to send data, but not to register meter +options: + name: + description: + - meter name + required: true + state: + description: + - Whether to create or remove the client from boundary + required: false + default: true + choices: ["present", "removed"] + aliases: [] + apiid: + description: + - Organizations boundary API ID + required: true + default: null + choices: [] + aliases: [] + apikey: + description: + - Organizations boundary API KEY + required: true + default: null + choices: [] + aliases: [] + +notes: + - This module does not yet support tags. + +''' + +EXAMPLES=''' +- name: Create meter + boundary_meter: apiid=AAAAAA apikey=BBBBBB state=present name={{ inventory_hostname }}" + +- name: Delete meter + boundary_meter: apiid=AAAAAA apikey=BBBBBB state=removed 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://{API_HOST}/{APIID}/meters'.format(API_HOST=API_HOST, APIID=APIID) + elif action == "search": + return "https://{API_HOST}/{APIID}/meters?name={NAME}".format(API_HOST=API_HOST, APIID=APIID, NAME=NAME) + elif action == "certificates": + return "https://{API_HOST}/{APIID}/meters/{METER_ID}/{CERT_TYPE}.pem".format(API_HOST=API_HOST, APIID=APIID, METER_ID=METER_ID, CERT_TYPE=CERT_TYPE) + elif action == "tags": + return "https://{API_HOST}/{APIID}/meters/{METER_ID}/tags".format(API_HOST=API_HOST, APIID=APIID, METER_ID=METER_ID) + elif action == "delete": + return "https://{API_HOST}/{APIID}/meters/{METER_ID}".format(API_HOST=API_HOST, APIID=APIID, METER_ID=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 {auth}".format(auth=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 dirctory doesn't exist, create it + if not os.path.exists(CONFIG_DIRECTORY): + os.makedirs(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('/etc/bprobe/{CERT_TYPE}.pem'.format(CERT_TYPE=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 for searching") + + # 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 for deleting") + + 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 for certificate download") + + if result: + try: + cert_file_path = '/{CONFIG_DIR}/{CERT_TYPE}.pem'.format(CONFIG_DIR=CONFIG_DIRECTORY,CERT_TYPE=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', 'removed']), + name=dict(required=False), + apikey=dict(required=True), + apiid=dict(required=True), + tags=dict(required=False), + ) + ) + + state = module.params['state'] + NAME= module.params['name'] + APIKEY = module.params['apikey'] + APIID = module.params['apiid'] + TAGS = module.params['tags'] + + if state == "present": + (rc, result) = create_meter(module, NAME, APIID, APIKEY) + + if state == "removed": + (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