made changes as requested by mpdehaan and added code to remove key and cert pem files on removal of meter

This commit is contained in:
Curtis 2013-06-24 16:20:36 -06:00
parent 7983139383
commit b3df566399

View file

@ -1,5 +1,4 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
@ -7,11 +6,6 @@ Ansible module to add boundary meters.
(c) 2013, curtis <curtis@serverascode.com> (c) 2013, curtis <curtis@serverascode.com>
* 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 This file is part of Ansible
Ansible is free software: you can redistribute it and/or modify Ansible is free software: you can redistribute it and/or modify
@ -32,6 +26,7 @@ import json
import datetime import datetime
import urllib2 import urllib2
import base64 import base64
import os
DOCUMENTATION = ''' DOCUMENTATION = '''
@ -43,7 +38,8 @@ version_added: "1.3"
author: curtis@serverascode.com author: curtis@serverascode.com
requirements: requirements:
- Boundary API access - Boundary API access
- Boundary client is needed to send data, but not to register meter - bprobe is required to send data, but not to register a meter
- Python urllib2
options: options:
name: name:
description: description:
@ -54,22 +50,15 @@ options:
- Whether to create or remove the client from boundary - Whether to create or remove the client from boundary
required: false required: false
default: true default: true
choices: ["present", "removed"] choices: ["present", "absent"]
aliases: []
apiid: apiid:
description: description:
- Organizations boundary API ID - Organizations boundary API ID
required: true required: true
default: null
choices: []
aliases: []
apikey: apikey:
description: description:
- Organizations boundary API KEY - Organizations boundary API KEY
required: true required: true
default: null
choices: []
aliases: []
notes: notes:
- This module does not yet support tags. - This module does not yet support tags.
@ -81,7 +70,7 @@ EXAMPLES='''
boundary_meter: apiid=AAAAAA apikey=BBBBBB state=present name={{ inventory_hostname }}" boundary_meter: apiid=AAAAAA apikey=BBBBBB state=present name={{ inventory_hostname }}"
- name: Delete meter - name: Delete meter
boundary_meter: apiid=AAAAAA apikey=BBBBBB state=removed name={{ inventory_hostname }}" boundary_meter: apiid=AAAAAA apikey=BBBBBB state=absent name={{ inventory_hostname }}"
''' '''
@ -91,55 +80,55 @@ try:
except ImportError: except ImportError:
HAS_URLLIB2 = False HAS_URLLIB2 = False
API_HOST = "api.boundary.com" api_host = "api.boundary.com"
CONFIG_DIRECTORY = "/etc/bprobe" config_directory = "/etc/bprobe"
# "resource" like thing or APIKEY? # "resource" like thing or apikey?
def auth_encode(APIKEY): def auth_encode(apikey):
auth = base64.standard_b64encode(APIKEY) auth = base64.standard_b64encode(apikey)
auth.replace("\n", "") auth.replace("\n", "")
return auth return auth
def build_url(NAME, APIID, action, METER_ID=None, CERT_TYPE=None): def build_url(name, apiid, action, meter_id=None, cert_type=None):
if action == "create": if action == "create":
return 'https://{API_HOST}/{APIID}/meters'.format(API_HOST=API_HOST, APIID=APIID) return 'https://{api_host}/{apiid}/meters'.format(api_host=api_host, apiid=apiid)
elif action == "search": elif action == "search":
return "https://{API_HOST}/{APIID}/meters?name={NAME}".format(API_HOST=API_HOST, APIID=APIID, NAME=NAME) return "https://{api_host}/{apiid}/meters?name={name}".format(api_host=api_host, apiid=apiid, name=name)
elif action == "certificates": 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) 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": elif action == "tags":
return "https://{API_HOST}/{APIID}/meters/{METER_ID}/tags".format(API_HOST=API_HOST, APIID=APIID, METER_ID=METER_ID) return "https://{api_host}/{apiid}/meters/{meter_id}/tags".format(api_host=api_host, apiid=apiid, meter_id=meter_id)
elif action == "delete": elif action == "delete":
return "https://{API_HOST}/{APIID}/meters/{METER_ID}".format(API_HOST=API_HOST, APIID=APIID, METER_ID=METER_ID) 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): def http_request(name, apiid, apikey, action, meter_id=None, cert_type=None):
if METER_ID is None: if meter_id is None:
url = build_url(NAME, APIID, action) url = build_url(name, apiid, action)
else: else:
if CERT_TYPE is None: if cert_type is None:
url = build_url(NAME, APIID, action, METER_ID) url = build_url(name, apiid, action, meter_id)
else: else:
url = build_url(NAME, APIID, action, METER_ID, CERT_TYPE) url = build_url(name, apiid, action, meter_id, cert_type)
auth = auth_encode(APIKEY) auth = auth_encode(apikey)
request = urllib2.Request(url) request = urllib2.Request(url)
request.add_header("Authorization", "Basic {auth}".format(auth=auth)) request.add_header("Authorization", "Basic {auth}".format(auth=auth))
request.add_header("Content-Type", "application/json") request.add_header("Content-Type", "application/json")
return request return request
def create_meter(module, NAME, APIID, APIKEY): def create_meter(module, name, apiid, apikey):
meters = search_meter(module, NAME, APIID, APIKEY) meters = search_meter(module, name, apiid, apikey)
if len(meters) > 0: if len(meters) > 0:
# If the meter already exists, do nothing # If the meter already exists, do nothing
module.exit_json(status="Meter " + NAME + " already exists",changed=False) module.exit_json(status="Meter " + name + " already exists",changed=False)
else: else:
# If it doesn't exist, create it # If it doesn't exist, create it
request = http_request(NAME, APIID, APIKEY, action="create") 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 # A create request seems to need a json body with the name of the meter in it
body = '{"name":"' + NAME + '"}' body = '{"name":"' + name + '"}'
request.add_data(body) request.add_data(body)
try: try:
@ -147,54 +136,58 @@ def create_meter(module, NAME, APIID, APIKEY):
except urllib2.URLError, e: except urllib2.URLError, e:
module.fail_json(msg="Failed to connect to api host to create meter") module.fail_json(msg="Failed to connect to api host to create meter")
# If the config dirctory doesn't exist, create it # If the config directory doesn't exist, create it
if not os.path.exists(CONFIG_DIRECTORY): if not os.path.exists(config_directory):
os.makedirs(CONFIG_DIRECTORY) try:
os.makedirs(config_directory)
except:
module.fail_json("Could not create " + config_directory)
# Download both cert files from the api host # Download both cert files from the api host
types = ['key', 'cert'] types = ['key', 'cert']
for cert_type in types: for cert_type in types:
try: try:
# If we can't open the file it's not there, so we should download it # 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)) cert_file = open('/etc/bprobe/{cert_type}.pem'.format(cert_type=cert_type))
except IOError: except IOError:
# Now download the file... # Now download the file...
rc = download_request(module, NAME, APIID, APIKEY, cert_type) rc = download_request(module, name, apiid, apikey, cert_type)
if rc == False: if rc == False:
module.fail_json("Download request for " + cert_type + ".pem failed") module.fail_json("Download request for " + cert_type + ".pem failed")
return 0, "Meter " + NAME + " created" return 0, "Meter " + name + " created"
def search_meter(module, NAME, APIID, APIKEY): def search_meter(module, name, apiid, apikey):
request = http_request(NAME, APIID, APIKEY, action="search") request = http_request(name, apiid, apikey, action="search")
try: try:
result = urllib2.urlopen(request) result = urllib2.urlopen(request)
except urllib2.URLError, e: except urllib2.URLError, e:
module.fail_json("Failed to connect to api host for searching") module.fail_json("Failed to connect to api host to search for meter")
# Return meters # Return meters
return json.loads(result.read()) return json.loads(result.read())
def get_meter_id(module, NAME, APIID, APIKEY): def get_meter_id(module, name, apiid, apikey):
# In order to delete the meter we need its id # In order to delete the meter we need its id
meters = search_meter(module, NAME, APIID, APIKEY) meters = search_meter(module, name, apiid, apikey)
if len(meters) > 0: if len(meters) > 0:
return meters[0]['id'] return meters[0]['id']
else: else:
return None return None
def delete_meter(module, NAME, APIID, APIKEY): def delete_meter(module, name, apiid, apikey):
meter_id = get_meter_id(module, NAME, APIID, APIKEY) meter_id = get_meter_id(module, name, apiid, apikey)
if meter_id is None: if meter_id is None:
return 1, "Meter does not exist, so can't delete it" return 1, "Meter does not exist, so can't delete it"
else: else:
action = "delete" action = "delete"
request = http_request(NAME, APIID, APIKEY, action, meter_id) request = http_request(name, apiid, apikey, action, meter_id)
# See http://stackoverflow.com/questions/4511598/how-to-make-http-delete-method-using-urllib2 # 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 # urllib2 only does GET or POST I believe, but here we need delete
request.get_method = lambda: 'DELETE' request.get_method = lambda: 'DELETE'
@ -202,26 +195,35 @@ def delete_meter(module, NAME, APIID, APIKEY):
try: try:
result = urllib2.urlopen(request) result = urllib2.urlopen(request)
except urllib2.URLError, e: except urllib2.URLError, e:
module.fail_json("Failed to connect to api host for deleting") module.fail_json("Failed to connect to api host to delete meter")
return 0, "Meter " + NAME + " deleted" # 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 = '/{config_directory}/{cert_type}.pem'.format(config_directory=config_directory,cert_type=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")
def download_request(module, NAME, APIID, APIKEY, CERT_TYPE): return 0, "Meter " + name + " deleted"
meter_id = get_meter_id(module, NAME, APIID, APIKEY) def download_request(module, name, apiid, apikey, cert_type):
meter_id = get_meter_id(module, name, apiid, apikey)
if meter_id is not None: if meter_id is not None:
action = "certificates" action = "certificates"
request = http_request(NAME, APIID, APIKEY, action, meter_id, CERT_TYPE) request = http_request(name, apiid, apikey, action, meter_id, cert_type)
try: try:
result = urllib2.urlopen(request) result = urllib2.urlopen(request)
except urllib2.URLError, e: except urllib2.URLError, e:
module.fail_json("Failed to connect to api host for certificate download") module.fail_json("Failed to connect to api host to download certificate")
if result: if result:
try: try:
cert_file_path = '/{CONFIG_DIR}/{CERT_TYPE}.pem'.format(CONFIG_DIR=CONFIG_DIRECTORY,CERT_TYPE=CERT_TYPE) cert_file_path = '/{config_directory}/{cert_type}.pem'.format(config_directory=config_directory,cert_type=cert_type)
body = result.read() body = result.read()
cert_file = open(cert_file_path, 'w') cert_file = open(cert_file_path, 'w')
cert_file.write(body) cert_file.write(body)
@ -241,25 +243,23 @@ def main():
module = AnsibleModule( module = AnsibleModule(
argument_spec=dict( argument_spec=dict(
state=dict(required=True, choices=['present', 'removed']), state=dict(required=True, choices=['present', 'absent']),
name=dict(required=False), name=dict(required=False),
apikey=dict(required=True), apikey=dict(required=True),
apiid=dict(required=True), apiid=dict(required=True),
tags=dict(required=False),
) )
) )
state = module.params['state'] state = module.params['state']
NAME= module.params['name'] name= module.params['name']
APIKEY = module.params['apikey'] apikey = module.params['apikey']
APIID = module.params['apiid'] apiid = module.params['apiid']
TAGS = module.params['tags']
if state == "present": if state == "present":
(rc, result) = create_meter(module, NAME, APIID, APIKEY) (rc, result) = create_meter(module, name, apiid, apikey)
if state == "removed": if state == "absent":
(rc, result) = delete_meter(module, NAME, APIID, APIKEY) (rc, result) = delete_meter(module, name, apiid, apikey)
if rc != 0: if rc != 0:
module.fail_json(msg=result) module.fail_json(msg=result)