Commit 445c8884 by Curtis

made changes as requested by mpdehaan and added code to remove key and cert pem…

made changes as requested by mpdehaan and added code to remove key and cert pem files on removal of meter
parent 56bfae36
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
""" """
...@@ -7,11 +6,6 @@ Ansible module to add boundary meters. ...@@ -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 ...@@ -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" ...@@ -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: ...@@ -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=''' ...@@ -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: ...@@ -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): ...@@ -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): ...@@ -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")
# 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")
return 0, "Meter " + NAME + " deleted" return 0, "Meter " + name + " deleted"
def download_request(module, NAME, APIID, APIKEY, CERT_TYPE): def download_request(module, name, apiid, apikey, cert_type):
meter_id = get_meter_id(module, NAME, APIID, APIKEY) 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(): ...@@ -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)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment