Source code for bloxone.bloxone

#!/usr/local/bin/python3
# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4
'''
------------------------------------------------------------------------

 Description:

 Module to provide class hierachy to simplify access to the BloxOne APIs

 Date Last Updated: 20200713

 Todo:

 Copyright (c) 2020 Chris Marrison / Infoblox

 Redistribution and use in source and binary forms,
 with or without modification, are permitted provided
 that the following conditions are met:

 1. Redistributions of source code must retain the above copyright
 notice, this list of conditions and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright
 notice, this list of conditions and the following disclaimer in the
 documentation and/or other materials provided with the distribution.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 POSSIBILITY OF SUCH DAMAGE.

------------------------------------------------------------------------
'''
__version__ = '0.3.0'
__author__ = 'Chris Marrison'
__author_email__ = 'chris@infoblox.com'

import logging
import configparser
import requests
import json

# ** Global Vars **
cspurl = "https://csp.infoblox.com/api"


# ** Facilitate ini file for basic configuration including API Key

[docs]def read_b1_ini(ini_filename): ''' Open and parse ini file Parameters: ini_filename (str): name of inifile Returns: config (dict): Dictionary of BloxOne configuration elements ''' # Local Variables cfg = configparser.ConfigParser() config = {} ini_keys = ['url', 'api_version', 'api_key'] # Attempt to read api_key from ini file try: cfg.read(ini_filename) except configparser.Error as err: logging.error(err) # Look for TIDE section if 'BloxOne' in cfg: for key in ini_keys: # Check for key in BloxOne section if key in cfg['BloxOne']: config[key] = cfg['BloxOne'][key].strip("'\"") logging.debug('Key {} found in {}: {}'.format(key, ini_filename, config[key])) else: logging.warn('Key {} not found in BloxOne section.'.format(key)) config[key] = '' else: logging.warn('No BloxOne Section in config file: {}'.format(ini_filename)) config['api_key'] = '' return config
[docs]class b1: ''' Parent Class to simplify access to the BloxOne APIs for subclasses ''' def __init__(self, cfg_file='config.ini'): ''' Read ini file and set attributes Parametrers: cfg_file (str): Override default ini filename ''' self.cfg = {} # Read ini file self.cfg = read_b1_ini(cfg_file) # Define generic header self.api_key = self.cfg['api_key'] self.headers = ( {'content-type': 'application/json', 'Authorization': 'Token ' + self.api_key} ) # Create base URLs # self.ep_url = self.cfg['url'] + '/api/host_app/' + self.cfg['api_version'] # self.fw_url = self.cfg['url'] + '/api/host_app/' + self.cfg['api_version'] self.base_url = self.cfg['url'] self.api_version = self.cfg['api_version'] self.ddi_url = self.base_url + '/api/ddi/' + self.api_version self.host_url = self.base_url + '/api/host_app/' + self.cfg['api_version'] self.dns_url = self.base_url + '/api/ddi/' + self.cfg['api_version'] + '/dns' self.ipam_url = self.base_url + '/api/ddi/' + self.cfg['api_version'] + '/ipam' self.dhcp_url = self.base_url + '/api/ddi/' + self.cfg['api_version'] +'/dhcp' # List of successful return codes self.return_codes_ok = [200, 201, 204] return def _add_params(self, url, **params): # Add params to API call URL if len(params): first_param = True for param in params.keys(): if first_param: url = url + '?' first_param = False else: url = url + '&' url = url + param + '=' + params[param] return url def _apiget(self, url): # Call BloxOne API try: response = requests.request("GET", url, headers=self.headers) # Catch exceptions except requests.exceptions.RequestException as e: logging.error(e) logging.debug("url: {}".format(url)) raise # Return response code and body text # return response.status_code, response.text return response def _apipost(self, url, body): # Call BloxOne API try: response = requests.request("POST", url, headers=self.headers, data=body) # Catch exceptions except requests.exceptions.RequestException as e: logging.error(e) logging.debug("url: {}".format(url)) logging.debug("body: {}".format(body)) raise # Return response code and body text return response def _apidelete(self, url): # Call BloxOne API try: response = requests.request("DELETE", url, headers=self.headers) # Catch exceptions except requests.exceptions.RequestException as e: logging.error(e) logging.debug("url: {}".format(url)) raise # Return response code and body text return response def _apiput(self, url, body): # Call BloxOne API try: response = requests.request("PUT", url, headers=self.headers, data=body) # Catch exceptions except requests.exceptions.RequestException as e: logging.error(e) logging.debug("url: {}".format(url)) logging.debug("body: {}".format(body)) raise # Return response code and body text return response def _apipatch(self, url, body): # Call BloxOne API try: response = requests.request("PATCH", url, headers=self.headers, data=body) # Catch exceptions except requests.exceptions.RequestException as e: logging.error(e) logging.debug("url: {}".format(url)) logging.debug("body: {}".format(body)) raise # Return response code and body text return response def _use_obj_id(self, url, id="", action=""): ''' Update URL for use with object id Parameters: id (str): Bloxone Object id nextip (bool): use nextavailableip Returns: url (str): Updated url ''' # Check for id and next available IP if id: url = url + '/' + id if action: url = url + '/' + action else: if action: logging.debug("Action {} not supported without " "a specified ovject id.") return url
[docs]class b1platform(b1): ''' Class to simplify access to the BloxOne Platform APIs '''
[docs] def get(self, objpath, id="", action="", **params): ''' Generic get object wrapper for platform calls Parameters: objpath (str): Swagger object path id (str): Optional Object ID action (str): Optional object action, e.g. "nextavailableip" Returns: response (obj): Requests response object ''' # Build url url = self.host_url + objpath url = self._use_obj_id(url,id=id) url = self._add_params(url, **params) logging.debug("URL: {}".format(url)) response = self._apiget(url) return response
[docs] def create(self, objpath, body=""): ''' Generic create object wrapper for platform objects Parameters: objpath (str): Swagger object path body (str): JSON formatted data payload Returns: response (obj): Requests response object ''' # Build url url = self.host_url + objpath # Make API Call response = self._apipost(url, body) return response
[docs] def delete(self, objpath, id=""): ''' Generic delete object wrapper for platform objects Parameters: objpath (str): Swagger object path id (str): Object id to delete Returns: response (obj): Requests response object ''' # Build url url = self.host_url + objpath url = self._use_obj_id(url, id=id) # Make API Call response = self._apidelete(url) return response
[docs] def update(self, objpath, id="", body=""): ''' Generic create object wrapper for ddi objects Parameters: objpath (str): Swagger object path body (str): JSON formatted data payload Returns: response (obj): Requests response object ''' # Build url url = self.host_url + objpath url = self._use_obj_id(url, id=id) # Make API Call response = self._apiput(url, body) return response
[docs] def get_tags(self, objpath, id=""): ''' Get tags for an object id Parameters: objpath (str): Swagger object path id (str): id of object Returns: tags (dict): Dictionary of current tags or empty dict if none .. todo:: * make generic, however, this requires the below * Lookup dictionary of 'required fields' per object type ''' tags = {} response = self.get(objpath, id=id, _fields="tags") if response.status_code in self.return_codes_ok: tags = json.loads(response.text) tags = tags['result'] else: tags = {} return tags
# *** Platform API Requests ***
[docs] def on_prem_hosts(self, **params): ''' Method to retrieve On Prem Hosts (undocumented) Parameters: **params (dict): Generic API parameters Returns: response (obj): Requests response object ''' # Call BloxOne API response = self.get('/on_prem_hosts', **params) # Return response object return response
[docs] def oph_add_tag(self, id="", tagname="", tagvalue=""): ''' Method to add a tag to an existing On Prem Host Parameters: objpath (str): Swagger object path tagname (str): Name of tag to add tagvalue (str): Value to associate Returns: response (obj): Requests response object ''' # tags = self.get_tags('/on_prem_hosts', id=id) response = self.get('/on_prem_hosts', id=id, _fields="display_name,tags") if response.status_code in self.return_codes_ok: data = response.json()['result'] else: data = {} logging.debug("Existing tags: {}".format(data)) # Add new tag to data if tagname: data['tags'].update({tagname: tagvalue}) logging.debug("New tags: {}".format(data)) # Update object response = self.update('/on_prem_hosts', id=id, body=json.dumps(data)) return response
[docs] def oph_delete_tag(self, id="", tagname=""): ''' Method to delete a tag from an existing On Prem Host Parameters: objpath (str): Swagger object path tagname (str): Name of tag to add Returns: response (obj): Requests response object ''' # tags = self.get_tags('/on_prem_hosts', id=id) response = self.get('/on_prem_hosts', id=id, _fields="display_name,tags") if response.status_code in self.return_codes_ok: data = response.json()['result'] logging.debug("Existing tags: {}".format(data)) # Delete tag from data if tagname in data['tags'].keys(): data['tags'].pop(tagname, True) print(json.dumps(data)) logging.debug("New tags: {}".format(data)) # Update object response = self.update('/on_prem_hosts', id=id, body=json.dumps(data)) return response