Skip to content

Commit

Permalink
Merge pull request #5 from macvjuhu/development
Browse files Browse the repository at this point in the history
Development
  • Loading branch information
macvjuhu authored Jan 24, 2018
2 parents 1fd8468 + eb74f92 commit ddd6344
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 90 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ Easy to use REST Client for Firepower Management Center REST API
* Maintains REST connection with FMC
* Listing for a particular resource type supports fetching all results in single go
* Easy to add support for new resources or url endpoints
* Support for bulk create, leveraging FMC REST API where available or support in client
* Iterator for list operation

### Enhancements in future
* Support for bulk operations, leveraging FMC REST API where available or support in client
* Iterator for list operation
* Get by name

## How to use this library
Refer [How To](HOWTO.md)
Get this library from PyPI using 'pip install fmc_rest_client'.
Refer [How To](HOWTO.md) for further details on using this library.

11 changes: 5 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,20 @@ Easy to use REST Client for Firepower Management Center REST API

Features
~~~~~~~~

- Create object for FMC REST resources and perform CRUD operations
- Maintains REST connection with FMC
- Listing for a particular resource type supports fetching all results
in single go
- Easy to add support for new resources or url endpoints
- Support for bulk create, leveraging FMC REST API where available
or support in client
- Iterator for list operation

Enhancements in future
~~~~~~~~~~~~~~~~~~~~~~

- Support for bulk operations, leveraging FMC REST API where available
or support in client
- Iterator for list operation
- Get by name

How to use this library
----------
Refer HOWTO.md
Get this library from PyPI using 'pip3 install fmc_rest_client'.
Refer HOWTO.md for further details on using this library.
96 changes: 85 additions & 11 deletions fmc_rest_client/core/base_clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,46 @@ def __init__(self, code, message=None):
self.code = code
self.message = message

class RESTListIterator:
"""
"""
def __init__(self, resource, rest_client):
self.current_index = 0
self.offset = 0
self.resource = resource
self.rest_client = rest_client
self.list_cache, paging = rest_client._list(resource)
self.total = paging['count']
self.page_size = paging['limit']

def __iter__(self):
return self

def __next__(self):
return self.next()

def next(self):
if self.current_index < self.total:
cache_size = len(self.list_cache)
logger.debug('Fetching item number {} out of {}'.format(self.current_index, self.total))
logger.debug('Current offset {} and Cache size {}'.format(self.offset, cache_size))
if self.current_index == self.offset + cache_size:
#fetch next page if we are done reading cached page
self.offset = self.current_index
logger.debug('Fetching page starting at offset {}'.format(self.offset))
self.list_cache, paging = self.rest_client._list(self.resource, self.offset)
if paging['count'] != self.total:
logger.warning("Resource size changed on server")
self.total = paging['count']
if self.current_index > self.total:
logger.warning("Resource size got reduced than current iteration")
raise StopIteration("Resource size got reduced.")
resource = self.list_cache[self.current_index - self.offset]
self.current_index += 1
return resource
else:
raise StopIteration()

class FMCRawRestClient(object):
def __init__(self, server, username=None, password=None, auth_token=None, domain='default'):
Expand Down Expand Up @@ -60,10 +100,12 @@ def get_auth_token(self):
auth_token = auth_headers.get('X-auth-access-token', default=None)
if auth_token is None:
logger.error('auth_token not found.')
raise Exception('auth_token not found')
raise ResourceException(ResourceException.AUTH_FAILED)
else:
logger.debug('Got auth_token - ' + auth_token)
return auth_token
except ResourceException as e:
raise e
except Exception as err:
# logger.debug('Error in generating auth token --> '+str(err))
raise Exception('Error in generating auth token --> ' + str(err))
Expand Down Expand Up @@ -96,8 +138,18 @@ def rest_call(self, method, url_path, post_data=None, offset=0, expanded=False):
return response_json
except ResourceException as e:
if e.code == ResourceException.AUTH_FAILED and reauth_retry_count > 0:
reauth_retry_count -= 1
self.auth_token = self.get_auth_token()
#we may get auth-failed when calling get_auth_token as well, that's why this retry loop
while True:
try:
reauth_retry_count -= 1
logger.debug('Retrying re-auth, retry left {}.'.format(reauth_retry_count))
self.auth_token = self.get_auth_token()
break # break inner loop for get_auth_token
except ResourceException as e:
if e.code == ResourceException.AUTH_FAILED and reauth_retry_count > 0:
continue
else:
raise e
elif e.code == ResourceException.TOO_MANY_REQUESTS and request_retry_count > 0:
request_retry_count -= 1
time.sleep(5)
Expand Down Expand Up @@ -185,7 +237,21 @@ def _http_request(self, method, url, post_data, headers, offset=0, expanded=Fals
raise e
return response

def _list(self, resource, offset=None):
"""
The following json structure is retruned from FMC in case of list call
{
"items": [ <resource1>, ... ],
"paging": {
"offset": 0,
"limit": 25,
"count": 162,
"next": [...],
"pages": 7
}
}
"""
def _list(self, resource, offset=None, limit=None):
url_path = resource.get_api_path()
json_resp = self.rest_call('list', url_path, offset=offset, expanded=True)
objs = []
Expand All @@ -196,10 +262,13 @@ def _list(self, resource, offset=None):
obj.json_load(json_obj)
objs.append(obj)
# print(objs)
pages = 0
paging = {}
if 'paging' in json_resp:
pages = int(json_resp['paging']['pages'])
return objs, pages
paging['pages'] = int(json_resp['paging']['pages'])
paging['offset'] = int(json_resp['paging']['offset'])
paging['count'] = int(json_resp['paging']['count'])
paging['limit'] = int(json_resp['paging']['limit'])
return objs, paging

######## Raw HTTP calls ###########
## these uses the raw payload which is json ##
Expand Down Expand Up @@ -266,9 +335,11 @@ def load(self, resource):
url_path = resource.get_api_path()
if resource.id:
url_path += '/' + str(resource.id)
json_resp = self.get(url_path)
resource.json_load(json_resp)
return resource
json_resp = self.get(url_path)
resource.json_load(json_resp)
return resource
else:
return self.list(resource)

def get(self, resource):
if isinstance(resource, str):
Expand Down Expand Up @@ -310,7 +381,7 @@ def list(self, resource, offset=0, limit=0):
result = self._list(resource, offset=offset)
objs.extend(result[0])

pages = result[1] - 1 # we already read one page
pages = result[1]['pages'] - 1 # we already read one page
while pages > 0:
offset += len(result[0])
logger.debug('pages ' + str(pages))
Expand All @@ -327,3 +398,6 @@ def list(self, resource, offset=0, limit=0):
class FMCRestClient(FMCBaseRestClient):
def __init__(self, server, username=None, password=None, auth_token=None, domain='default'):
super(FMCRestClient, self).__init__(server, username, password, auth_token, domain)

def list_iterator(self, resource):
return RESTListIterator(resource, self)
23 changes: 15 additions & 8 deletions samples/generate_network_host_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,18 @@
action='create'

def usage():
print('script -s <fmc server url> -u <username> -p <password> -n <object_count> [-p <obj_prefix>')
print('script -s <fmc server url> -u <username> -p <password> -n <object_count> [-p <obj_prefix>] [-b <obj_name_start_indx>]')

def parse_args(argv):
fmc_server_url = None
username = None
password = None
object_count = None
object_name_prefix = 'NWHostObject_'
start_index = 0

try:
opts, args = getopt.getopt(argv,'hu:p:s:n:f:a:', ['file='])
opts, args = getopt.getopt(argv,'hu:p:s:n:f:a:b:', ['file='])
except getopt.GetoptError as e:
print(str(e))
usage()
Expand All @@ -39,6 +41,8 @@ def parse_args(argv):
password = arg
elif opt == '-s':
fmc_server_url = arg
elif opt == '-b':
start_index = int(arg)
elif opt == '-n':
object_count = int(arg)
elif opt == '-f':
Expand All @@ -51,10 +55,10 @@ def parse_args(argv):
#sys.exit(2)
else:
pass
if not (object_count and fmc_server_url and username and password) :
if (object_count == None or fmc_server_url == None or username == None or password == None):
usage()
sys.exit(2)
return username, password, fmc_server_url, object_count, object_name_prefix
return username, password, fmc_server_url, object_count, object_name_prefix, start_index

"""
Increment the last index number of string , ex: obj_1 to obj_2
Expand Down Expand Up @@ -96,13 +100,13 @@ def ipRangeByCount(start_ip, total_ip):
i += 1
return ip_range

def create_range_objects(rest_client, nwObjCount, nwObjName, first_nwObjvalue, last_nwObjvalue=None):
def create_range_objects(rest_client, nwObjCount, nwObjName, first_nwObjvalue, start_index=0, last_nwObjvalue=None):
if last_nwObjvalue:
iprange = ipRange(first_nwObjvalue, last_nwObjvalue)
else:
iprange = ipRangeByCount(first_nwObjvalue, nwObjCount)
for i in range(1, nwObjCount+1):
key=nwObjName+str(i)
key=nwObjName+str(i + start_index)
ip_index=i%len(iprange)
print('Creating object {} = {}'.format(key, iprange[ip_index]))
value=str(iprange[ip_index])
Expand All @@ -116,11 +120,14 @@ def create_range_objects(rest_client, nwObjCount, nwObjName, first_nwObjvalue, l

if __name__ == "__main__":
start_time = datetime.now().replace(microsecond=0)
username, password, fmc_server_url, object_count, object_name_prefix = parse_args(sys.argv[1:])
username, password, fmc_server_url, object_count, object_name_prefix, start_index = parse_args(sys.argv[1:])
print('Connecting to FMC {} ...'.format(fmc_server_url))
rest_client = FMCRestClient(fmc_server_url, username, password)
print('Connected Successfully')
print("Objects to be created:", object_count)
create_range_objects(rest_client, object_count, object_name_prefix, "10.10.10.1")
if object_count > 0:
create_range_objects(rest_client, object_count, object_name_prefix, "10.10.10.1", start_index)
end_time = datetime.now().replace(microsecond=0)
print("Script completed in {}s.".format(str(end_time - start_time)))


101 changes: 101 additions & 0 deletions samples/iterate_objects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/bin/env python3
import getopt
import logging
import re
import sys
from datetime import datetime

from fmc_rest_client import FMCRestClient
from fmc_rest_client import ResourceException
from fmc_rest_client.resources import *

logging.basicConfig(level=logging.DEBUG)

obj_types = []
types_map = {
'networks': [ NetworkGroup(), Host() , Network(), Range()],
'ports': [PortObjectGroup(), Port(), ICMPV4Object(), ICMPV6Object() ]
}
supported_types = ['AccessPolicy', 'FtdNatPolicy', 'NetworkGroup', 'Host' , 'Network', 'Range', 'SecurityZone', 'InterfaceGroup', 'PortObjectGroup', 'Port', 'ICMPV4Object', 'ICMPV6Object']
def usage():
print('script -s <fmc server url> -u <username> -p <password> -t <comma separated types> [-r <repeat-count>]')

def parse_args(argv):
fmc_server_url = None
username = None
password = None
object_types = []
#whole operation will be repeated this many times
repeat = 1

try:
opts, args = getopt.getopt(argv,'hu:p:s:t:r:', ['--types'])
except getopt.GetoptError as e:
print(str(e))
usage()
sys.exit(2)
for opt, arg in opts:
if opt == '-h':
usage()
sys.exit()
elif opt == '-u':
username = arg
elif opt == '-p':
password = arg
elif opt == '-s':
fmc_server_url = arg
elif opt == '-t' or opt == '--types':
object_types = process_types_arg(arg)
elif opt == '-r' or opt == '--repeat':
repeat = int(arg)
else:
usage()
sys.exit(2)

if (len(object_types) == 0 or fmc_server_url == None or username == None or password == None):
usage()
sys.exit(2)
return username, password, fmc_server_url, object_types, repeat

def process_types_arg(types):
type_list = types.split(',')
msg = "Incorrect objects specified along with '-t' option. Pass the comma separated list of following - \n\t{}. " \
"\nYou can also use - 'networks' for all network type objects, 'ports' for all port type objects.".format(supported_types)
if len(type_list) == 0:
print(msg)
sys.exit(2)
obj_list = []

for t in type_list:
if t in types_map:
obj_list.extend(types_map[t])
elif t in globals():
obj_list.append(globals()[t]())
if len(obj_list) == 0:
print(msg)
sys.exit(2)
print('Types selected for iteration: {}'.format(list(map(lambda x: x.__class__.__name__, obj_list))))
return obj_list


if __name__ == "__main__":
username, password, fmc_server_url, object_types, repeat = parse_args(sys.argv[1:])
start_time = datetime.now().replace(microsecond=0)
print('Connecting to FMC {} ...'.format(fmc_server_url))
rest_client = FMCRestClient(fmc_server_url, username, password)
end_time = datetime.now().replace(microsecond=0)
print('Connected Successfully in {}s'.format(str(end_time - start_time)))
start_time = datetime.now().replace(microsecond=0)
for r in range(repeat):
for obj_type in object_types:
count = 0
print('Iterating objects of type {} ...'.format(type(obj_type).__name__))
for resource in rest_client.list_iterator(obj_type):
print("Resource name " + resource.name)
count += 1
print('Total {} objects of type {} iterated.'.format(count, type(obj_type).__name__))
end_time = datetime.now().replace(microsecond=0)
print('Script completed in {}s.'.format(str(end_time - start_time)))



Loading

0 comments on commit ddd6344

Please sign in to comment.