CLI tool for working with BIG-IQ Regkey license pools

Problem this snippet solves:

A BIG-IQ license manager can import base regkeys into a regkey pool and activate them. It cannot import addon keys from a csv along with the base regkey or use a csv to install addon keys to previously Activated offerings. Doing a large number of these one at a time in the GUI can be time-consuming. This script makes it possible to feed the keys as arguments when running the script. It only installs one license at a time but makes it possible to loop over a file(s) to automate the process.

This script can be used to install base regkeys with or without the addonkeys. It can also install addon keys to an existing offering. It cannot be run locally on a BIG-IQ because some modules are not available for import.

How to use this snippet:

./reg_pool_tool.py -h
usage: reg_pool_tool.py [-h] [-d] [-v] [-a ADDRESS] [-u USERNAME]
                        [-p PASSWORD] [-l] [-o POOL_UUID] [-r REG_KEY]
                        [-A ADD_ON_KEY_LIST] [-i INSTALL_POOL_UUID]
                        [-m MODIFY_POOL_UUID]

This is a tool for workingwith regkey pool on BIG-IQ

optional arguments:
  -h, --help            show this help message and exit
  -d, --debug           enable debug
  -v, --verbose         enable verbose for options that have it
  -a ADDRESS, --address ADDRESS
                        IP address of the target host
  -u USERNAME, --username USERNAME
                        username for auth to host
  -p PASSWORD, --password PASSWORD
                        password for auth to host
  -l, --list-pools      list the UUIDs for existing regkey pools, requires no
                        args
  -o POOL_UUID, --offerings POOL_UUID
                        take UUID of pool as arg and list the offerings for a
                        pool use -v to also show the active modules
  -r REG_KEY, --regkey REG_KEY
                        takes and stores the regkey for use in other options
  -A ADD_ON_KEY_LIST, --add-on-keys ADD_ON_KEY_LIST
                        takes string of comma sep addon keys for use by other
                        options
  -i INSTALL_POOL_UUID, --install-offering INSTALL_POOL_UUID
                        takes pool UUID as arg and installs new
                        offering,requires -r, -A can be used to install addon
                        keys atthe same time
  -m MODIFY_POOL_UUID, --modify-offering-addons MODIFY_POOL_UUID
                        takes pool UUID as arg and installs addon to
                        offering,requires -A [addon_key_list] and -r reg_key

Examples:

List regkey pools:

./reg_pool_tool.py -a 192.0.44 -l

List the offerings in a regkey pool:

./reg_pool_tool.py -a 192.0.2.44 -o 07c77c7f-3170-4b17-93de-31e4f39e4709 

Installing a new regkey with addon:

./reg_pool_tool.py -a 192.0.2.44 -i 5678d528-daac-4430-a87e-b67b87acfe20 -r B6083-41023-70161-19033-9429393 -A -A F659828-1850547

Modifying an existing offering adding a new addon key:

./reg_pool_tool.py -a 192.0.2.44 -m 5678d528-daac-4430-a87e-b67b87acfe20 -r K1260-99690-76028-13047-3993585 -A J984388-8738340

Code :

#!/usr/bin/env python
"""
reg_pool_tool.py
Python Version: 2.7.15
"""
from __future__ import print_function
import argparse
from base64 import b64encode
import json
#from pprint import pprint
import sys
import time
import urllib3
import requests


### Arguments parsing section ###
def cmd_args():
    """Handles command line arguments given."""
    parser = argparse.ArgumentParser(description='This is a tool for working'
                                                 'with regkey pool on BIG-IQ')
    parser.add_argument('-d',
                        '--debug',
                        action="store_true",
                        default=False,
                        help='enable debug')
    parser.add_argument('-v',
                        '--verbose',
                        action="store_true",
                        default=False,
                        help='enable verbose for options that have it')
    parser.add_argument('-a',
                        '--address',
                        action="store",
                        dest="address",
                        help='IP address of the target host')
    parser.add_argument('-u',
                        '--username',
                        action="store",
                        dest="username",
                        default='admin',
                        help='username for auth to host')
    parser.add_argument('-p',
                        '--password',
                        action="store",
                        dest="password",
                        default='admin',
                        help='password for auth to host')
    parser.add_argument('-l',
                        '--list-pools',
                        action="store_true",
                        default=False,
                        help='list the UUIDs for existing regkey pools, requires no args')
    parser.add_argument('-o',
                        '--offerings',
                        action="store",
                        dest="pool_uuid",
                        help='take UUID of pool as arg and list the offerings for a pool'
                             ' use -v to also show the active modules')
    parser.add_argument('-r',
                        '--regkey',
                        action="store",
                        dest="reg_key",
                        help='takes and stores the regkey for use in other options')
    parser.add_argument('-A',
                        '--add-on-keys',
                        action="store",
                        dest="add_on_key_list",
                        help='takes string of comma sep addon keys for use by other options')
    parser.add_argument('-i',
                        '--install-offering',
                        action="store",
                        dest="install_pool_uuid",
                        help='takes pool UUID as arg and installs new offering,'
                             'requires -r, -A can be used to install addon keys at'
                             'the same time')
    parser.add_argument('-m',
                        '--modify-offering-addons',
                        action="store",
                        dest="modify_pool_uuid",
                        help='takes pool UUID as arg and installs addon to offering,'
                             'requires -A [addon_key_list] and -r reg_key')


    parsed_arguments = parser.parse_args()

    # debug set print parser info
    if parsed_arguments.debug is True:
        print(parsed_arguments)


    # required args here
    if parsed_arguments.address is None:
        parser.error('-a target address is required, '
                     'use mgmt for local')
    if parsed_arguments.install_pool_uuid:
        if parsed_arguments.reg_key is None:
            parser.error('-i requires -r')
    if parsed_arguments.modify_pool_uuid:
        if parsed_arguments.add_on_key_list is None:
            parser.error('-m requires -A and -r')
        elif parsed_arguments.reg_key is None:
            parser.error('-m requires -A and -r')

    return parsed_arguments

### END ARGPARSE SECTION ###


def get_auth_token(address, user, password,
                   uri='/mgmt/shared/authn/login'):  # -> unicode
    """Get and auth token( to be used but other requests"""
    credentials_list = [user, ":", password]
    credentials = ''.join(credentials_list)
    user_and_pass = b64encode(credentials).decode("ascii")
    headers = {'Authorization':'Basic {}'.format(user_and_pass), 'Content-Type':'application/json'}
    post_data = '{"username":"' + user + '","password":"' + password +'"}'
    url_list = ['https://', address, uri]
    url = ''.join(url_list)
    try:
        request_result = requests.post(url, headers=headers, data=post_data, verify=False)
    except requests.exceptions.ConnectionError as connection_error:
        print(connection_error)
        sys.exit(1)
    except requests.exceptions.RequestException as request_exception:
        print(request_exception)
        sys.exit(1)

    #returns an instance of unicode that is an auth token with 300 dec timeout
    return request_result.json()['token']['token']


def get(url, auth_token, debug=False, return_encoding='json'):
    """Generic GET function. The return_encoding can be:'text', 'json', 'content'(binary),
    or raw """
    headers = {'X-F5-Auth-Token':'{}'.format(auth_token), 'Content-Type':'application/json'}

    get_result = requests.get(url, headers=headers, verify=False)

    if debug is True:
        print('GET request...')
        print('get_result.encoding: {}'.format(get_result.encoding))
        print('get_result.status_code: {}'.format(get_result.status_code))
        print('get_result.raise_for_status: {}'.format(
            get_result.raise_for_status()))

    if return_encoding == 'json':
        return get_result.json()
    elif return_encoding == 'text':
        return get_result.text
    elif return_encoding == 'content':
        return get_result.content
    elif return_encoding == 'raw':
        return get_result.raw()  # requires 'stream=True' in request


def post(url, auth_token, post_data, debug):
    """ generic POST function """
    headers = {'X-F5-Auth-Token':'{}'.format(auth_token), 'Content-Type':'application/json'}
    #post_data = '{"key":"value"}'
    try:
        post_result = requests.post(url, post_data, headers=headers, verify=False, timeout=10)
    except requests.exceptions.ConnectionError as connection_error:
        print ("Error Connecting: {}".format(connection_error))
        sys.exit(1)
    except requests.exceptions.RequestException as request_exception:
        print(request_exception)
        sys.exit(1)

    if debug is True:
        print('POST request...')
        print('post_result.encoding: {}'.format(post_result.encoding))
        print('post_result.status_code: {}'.format(post_result.status_code))
        print('post_result.raise_for_status: {}'.format(
            post_result.raise_for_status()))

    return post_result.json()


def patch(url, auth_token, patch_data, debug):
    """generic PATCH function"""
    headers = {'X-F5-Auth-Token':'{}'.format(auth_token), 'Content-Type':'application/json'}
    #patch_data = '{"key":"value"}'
    try:
        patch_result = requests.patch(url, patch_data, headers=headers, verify=False, timeout=10)
    except requests.exceptions.ConnectionError as connection_error:
        print ("Error Connecting: {}".format(connection_error))
        sys.exit(1)
    except requests.exceptions.RequestException as request_exception:
        print(request_exception)
        sys.exit(1)

    if debug is True:
        print('PATCH request...')
        print('patch_result.encoding: {}'.format(patch_result.encoding))
        print('patch_result.status_code: {}'.format(patch_result.status_code))
        print('patch_result.raise_for_status: {}'.format(
            patch_result.raise_for_status()))

    return patch_result.json()


class RegPool:
    """work with regkey pools"""
    def __init__(self, bigiq_address, username, password, debug=False):
        self.address = bigiq_address
        self.user = username
        self.password = password
        self.token = get_auth_token(self.address, self.user, self.password,
                                    uri='/mgmt/shared/authn/login')
        self.debug = debug

    def list_pool(self):
        """ Lists existing regkey pools """
        uri = '/mgmt/cm/device/licensing/pool/regkey/licenses'
        url_list = ['https://', self.address, uri]
        url = ''.join(url_list)
        pool_list_result = get(url, self.token, self.debug, return_encoding='json')
        # create a list of list [[,]]
        pool_list = []
        for entry in pool_list_result['items']:
            pool_list.append([entry['id'], entry['name']])

        return pool_list


    def list_offereings(self, regkey_pool_uuid):
        """Returns a list of offerings for the regkey pool UUID given"""
        url_list = ['https://', self.address, '/mgmt/cm/device/licensing/pool/regkey/licenses/',
                    regkey_pool_uuid, '/offerings']
        url = ''.join(url_list)
        offering_get_result = get(url, self.token, self.debug, return_encoding='json')
        offering_list_result = offering_get_result['items']

        # returns list of dictionaries of offerings
        return offering_list_result

    def install_offering(self, regkey_pool_uuid, new_regkey, add_on_keys):
        """
        :type regkey_pool_uuid: str
        :type new_regkey: str
        :type add_on_keys: str comma sep keys
        This fucntion installs a new base regkey and optional addon keys and
        installs, and attempts to activate. All status in printed by the function
        and there is no return statement. If it fails it will show that was well.
        """
        uri = '/mgmt/cm/device/licensing/pool/regkey/licenses/'
        url_list = ['https://', self.address, uri, regkey_pool_uuid, '/offerings/']
        url = ''.join(url_list)
        if add_on_keys:
            post_dict = {
                "regKey": new_regkey,
                "status": "ACTIVATING_AUTOMATIC",
                "addOnKeys": add_on_keys.split(','),
                "description" : ""
                }
        else:
            post_dict = {
                "regKey": new_regkey,
                "status": "ACTIVATING_AUTOMATIC",
                "description" : ""
            }
        # format dict to make sure it is json compliant
        payload = json.dumps(post_dict)
        try:
            post(url, self.token, payload, self.debug)
            print('\nSent base regkey {} to License server status:'.format(new_regkey))
        except:
            print('Post to License server failed')
            raise

        # poll for "eulaText"
        poll_result = {}
        attempt = 0 # keep track of tries and give up exit script after 10
        uri = '/mgmt/cm/device/licensing/pool/regkey/licenses/'
        url_list = ['https://', self.address, uri, regkey_pool_uuid, '/offerings/', new_regkey]
        url = ''.join(url_list)
        while "eulaText" not in poll_result.keys():
            try:
                poll_result = get(url, self.token, self.debug, return_encoding='json')
                print('\npoll {} for {}'.format(attempt +1, new_regkey))
                if "fail" in poll_result['message']:
                    sys.exit(poll_result['message'])
                print(poll_result['status'])
                print(poll_result['message'])
                time.sleep(5)
            except:
                print('Poll for eula failed for regkey {}'.format(new_regkey))
                raise
            attempt += 1
            if attempt == 5:
                sys.exit('Giving up after 5 tries to poll for EULA for RegKey')
        print('Finished polling...')

        # since we have eula back we need to patch back the eula
        # update "status" in dict
        poll_result["status"] = "ACTIVATING_AUTOMATIC_EULA_ACCEPTED"
        uri = '/mgmt/cm/device/licensing/pool/regkey/licenses/'
        url_list = ['https://', self.address, uri, regkey_pool_uuid, '/offerings/', new_regkey]
        url = ''.join(url_list)
        patch_dict = {"status":poll_result['status'], "eulaText": poll_result['eulaText']}
        patch_payload = json.dumps(patch_dict)
        print('sending PATCH to accept EULA for {}'.format(new_regkey))
        try:
            patch_result = patch(url, self.token, patch_payload, self.debug)
            print('{} for {}'.format(patch_result['message'], new_regkey))
            print(patch_result.get('status', 'ERROR: Status Not found in path_result'))
        except:
            raise


    def modify_offering_addon(self, regkey_pool_uuid, new_regkey, add_on_keys):
        """
        :type regkey_pool_uuid: str
        :type new_regkey: str
        :type add_on_keys: str comma sep keys
        
        Install addon keys to t previously installed Offereing
        """
        uri = '/mgmt/cm/device/licensing/pool/regkey/licenses/'
        url_list = ['https://', self.address, uri, regkey_pool_uuid, '/offerings/', new_regkey]
        url = ''.join(url_list)
        patch_dict = {"status": "ACTIVATING_AUTOMATIC", "addOnKeys": add_on_keys.split(',')}
        payload = json.dumps(patch_dict)

        try:
            patch(url, self.token, payload, self.debug)
            print('\nAdding {} addons for offering {} to License server status:'.format(
                add_on_keys.split(','), new_regkey))
        except:
            print('Post to License server failed')
            raise

        # poll for "eulaText"
        poll_result = {}
        attempt = 0 # keep track of tries and give up exit script after 10
        uri = '/mgmt/cm/device/licensing/pool/regkey/licenses/'
        url_list = ['https://', self.address, uri, regkey_pool_uuid, '/offerings/', new_regkey]
        url = ''.join(url_list)
        while not poll_result.get('status'):
            try:
                poll_result = get(url, self.token, self.debug, return_encoding='json')
                print('\npoll {} for {}, Addons: {}'.format(
                    attempt +1, new_regkey, add_on_keys.split(',')))
                if "fail" in poll_result['message']:
                    sys.exit(poll_result['message'])
                print(poll_result['status'])
                print(poll_result['message'])
                time.sleep(5)
            except:
                print('Poll for eula failed for regkey {}'.format(new_regkey))
                raise
            attempt += 1
            if attempt == 5:
                sys.exit('Giving up after 5 tries to poll for EULA for RegKey')
        print('Reactivation complete')
        print(poll_result.get('status'))
        print('Finished polling...')



if __name__ == "__main__":

    SCRIPT_NAME = sys.argv[0]

    # suppress ssl warning when disbling ssl verification with verify=False
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

    OPT = cmd_args()

    # STATIC GLOBALS
    ADDRESS = OPT.address
    RG_OBJECT = RegPool(OPT.address, OPT.username, OPT.password, OPT.debug)

    # -l
    if OPT.list_pools:
        LICENSE_LIST = RG_OBJECT.list_pool
        for pool in LICENSE_LIST():
            print('{}  {}'.format(pool[0], pool[1]))


    # -o, if -v included will also show moodules
    if OPT.pool_uuid:
        POOL_OFFERINGS = RG_OBJECT.list_offereings(OPT.pool_uuid)
        print('{0:35}  {1:20} {2:10}'.format('RegKey', 'Status', 'addOnKeys'))
        print(73 * '-')
        for offering in  POOL_OFFERINGS:
            if 'addOnKeys' in offering:
                print('{0:35}  {1:20} {2:10}'.format(offering['regKey'], offering['status'], 'YES'))
                # if verbose given list Active modules
                if OPT.verbose:
                    active_modules = offering.get('licenseText', 'Not available').splitlines()
                    for line in active_modules:
                        if line.startswith('active module'):
                            print('   {} '.format(line[:80]))
            else:
                # -v not given list without active module info
                print('{0:35}  {1:20} {2:10}'.format(offering['regKey'],
                                                     offering['status'],
                                                     offering.get('addOnKeys')))


    # -i install new offereing with or without an addon keys, requires -r
    if OPT.install_pool_uuid:
        RG_OBJECT.install_offering(OPT.install_pool_uuid, OPT.reg_key, OPT.add_on_key_list)

    # -m requires -r -A
    if OPT.modify_pool_uuid:
        RG_OBJECT.modify_offering_addon(OPT.modify_pool_uuid, OPT.reg_key, OPT.add_on_key_list)
Updated Jun 06, 2023
Version 2.0
No CommentsBe the first to comment