#!/usr/bin/env python # 2014 (c) Paul C. Buetow # I'm sorry, but this is my first Python program to find out if it's worth # using it. I'm more into Perl, and it will stay so. import argparse import base64 import bigsuds import getpass import os import pprint import re import readline import socket import sys import ConfigParser from inspect import isfunction from colorama import Fore, Back, Style __program__ = 'fapi' __version__ = 'VERSION_DEVEL' # Replaced by a Makefile target __prompt__ = '> ' # Default prompt __interactive__ = False class FapiBase(object): ''' Some base functionality used by any Fapi class ''' def __init__(self, args ): ''' Initializes the base ''' self._args = args def verbose(self, message): ''' Prints an informational message to stderr ''' if self._args.v: self.info(message, Style.DIM) def info(self, message, color=''): ''' Prints an informational message to stderr ''' if self._args.d: color = reset = '' else: reset = '' if color == '' else Style.RESET_ALL print >> sys.stderr, (color+'%s'+reset) % message def out(self, result, color=''): ''' Prints an iControl result to stdout ''' if result != None: if self._args.l and isinstance(result, (list, tuple)): print"\n".join(result) else: pp = pprint.PrettyPrinter(indent=4) pp.pprint(result) def print_version(self): ''' Prints out the version ''' self.info('This is %s version %s' % (__program__, __version__), Fore.RED) def print_synopsis(self): ''' Prints the full Synopsis string ''' if self._args.d: style = reset = '' else: style = Style.DIM reset = Style.RESET_ALL self.print_version() if not self._args.E: print "\n".join([ '', 'Synopsis:', style + ' monitor', ' monitor NAME get desc|state', ' node', ' node NODENAME create|delete', ' node NODENAME get detail|status|ratio', ' node NODENAME set ratio RATIO', ' pool', ' pool NAME add member MEMBER:PORT', ' pool NAME add monitor MONITOR', ' pool NAME create [LIST,OF,POOL,MEMBERS:PORT]', ' pool NAME delete', ' pool NAME del member MEMBER:PORT', ' pool NAME del monitors', ' pool NAME get detail|lbmethod|members|monitor|status', ' pool NAME set lbmethod LBMETHOD', ' poolmember NAME MEMBER:PORT get ratio', ' poolmember NAME MEMBER:PORT set ratio RATIO', ' vip', ' vip NAME create NETMASK', ' vip NAME get arp|detail|status|tgroup', ' vip NAME set arp enabled|disabled', ' vip NAME set tgroup TGROUP', ' vserver', ' vserver NAME create [protocol] [profile] [poolname] [mask]', ' vserver NAME delete', ' vserver NAME get brief|detail|status|vlan|persistence|profile|rule', ' vserver NAME set nat|pat disabled|enabled', ' vserver NAME set pool POOLNAME', ' vserver NAME set snat automap|none', ' vserver NAME set vlan [list,of,vlans,to,allow]', ' vserver NAME profile add PROFILENAME [PROFILECONTEXT]', ' vserver NAME profile remove PROFILENAME', ' vserver NAME rule add RULENAME [RULEPRIORITY]', ' vserver NAME rule remove RULENAME', ' profileclientssl', ' profileclientssl PROFILENAME create [SSLKEYNAME] [SSLCERTNAME]', ' profileclientssl PROFILENAME delete', ' profileclientssl PROFILENAME get detail' + reset, 'Please consult the manpage for examples.', 'Please run with -E for more (extended commands) synopsis!' ]) else: print "\n".join([ '', 'Extended synopsis.', 'The following partially needs admininstrator privileges on / and /Common:', style + ' -f Common -b balancer.example.com selfip', ' -f Common -b balancer.example.com selfip NAME create NETMASK VLANNAME [TGROUP]', ' -f Common -b balancer.example.com selfip NAME delete', ' -f Common -b balancer.example.com selfip NAME get address|detail|tgroup', ' -f Common -b balancer.example.com selfip NAME set tgroup TGROUP', ' -f Common -b balancer.example.com vlan', ' -f Common -b balancer.example.com vlan NAME create tagged VLANID internal|external|...', ' -f Common -b balancer.example.com vlan NAME delete', ' -f Common -b balancer.example.com vlan NAME get detail', ' -f Common tgroup', ' -f Common tgroup NAME add ha_order DEVICE ORDER', ' -f Common tgroup NAME create', ' -f Common tgroup NAME delete', ' -f Common tgroup NAME get detail', ' -f Common tgroup NAME get ha_order', ' -f Common tgroup NAME remove all_ha_orders', ' -f Common tgroup NAME remove ha_order DEVICE ORDER', ' -f / folder', ' -f / folder NAME create|delete', ' -f / folder NAME get detail|dgroup|tgroup', ' -f / folder NAME set dgroup|tgroup DGROUP|TGROUP' + reset ]) if __interactive__: print '(To exit interactive shell use \'Ctrl+d\')' class ArgumentParser(FapiBase): ''' The argument parser class ''' def __init__(self): ''' Initialize the argument parser ''' self._parser = parser = argparse.ArgumentParser(add_help=False) parser.add_argument('-a', action='store_true', help='Disables auto port in vserver name') parser.add_argument('-b', action='store', help='Forces to use the secified loadbalancer (overwrites -e)') parser.add_argument('-d', action='store_true', help='Disable colorful output') parser.add_argument('-E', action='store_true', help='Show extended synopsis') parser.add_argument('-e', action='store', help='Environment to use, e.g. dev,qa,live', default='qa') parser.add_argument('-f', action='store', help='Overwrite partition/folder from fapi.conf') parser.add_argument('-h', action='store_true', help='Print this help') parser.add_argument('-i', action='store_true', help='Interactive shell') parser.add_argument('-l', action='store_true', help='Use list output') parser.add_argument('-n', action='store_true', help='No-op (don\'t do actual stuff)') parser.add_argument('-v', action='store_true', help='Verbose') parser.add_argument('-V', action='store_true', help='Print program version') parser.add_argument('-C', action='store', help='Config file', default=os.path.expanduser('~') + '/.fapi.conf') parser.add_argument('what', nargs='?', help='node|pool|monitor|vserver|...') parser.add_argument('name', nargs='?', help='The object name to operate on') parser.add_argument('sub', nargs='?', help='First sub command') parser.add_argument('sub2', nargs='?', help='Second sub command') parser.add_argument('sub3', nargs='?', help='Third sub command') parser.add_argument('sub4', nargs='?', help='Fourth sub command') parser.add_argument('sub5', nargs='?', help='Fith sub command') def parse(self, arguments = []): ''' Parse the arguments ''' if not arguments: args = self._args = self._parser.parse_args() else: args = self._args = self._parser.parse_args(arguments) if args.h: self._parser.print_help() print '' self.print_synopsis() return None elif args.V: self.print_version() return None return args class Fapi(FapiBase): ''' The main F5 API Tool Object ''' def __init__(self, args): ''' Initialize the config file, username and password ''' FapiBase.__init__(self, args) self._config = ConfigParser.ConfigParser() self._config.read(args.C) if args.f != None: self._folder = args.f else: self._folder = self._config.get('fapi', 'folder') def __login(self): ''' Logs into the F5 BigIP SOAP API and changes the folder/adm. partition''' c = self._config a = self._args if c.has_option('fapi', 'username'): username = c.get('fapi', 'username') else: username = getpass.getuser() if c.has_option('fapi', 'password64'): password = base64.decodestring(c.get('fapi', 'password64')) else: prompt = 'Enter API password for user %s: ' % username password = getpass.getpass(prompt) self.verbose('Login to BigIP API with user %s' % username) # Try a comma separated lists of F5 boxes, use the first one loadbalancers = a.b if a.b else c.get('fapi', 'loadbalancers_' + a.e) err = None for loadbalancer in loadbalancers.split(','): try: self.verbose('Trying to login to \'%s\'' % loadbalancer) self._f5 = bigsuds.BIGIP(hostname = loadbalancer, username = username, password = password) self._f5.Management.Partition.set_active_partition(self._folder) self.verbose('Set folder/adm. partition to \'%s\'' % self._folder) err = None break except Exception, e: err = '%s:%s' % (loadbalancer, e) pass if err: raise Exception(err) def lookup(self, what): ''' Does a DNS lookup to fetch the name (mostly FQDN) and the IPs hostname will be looked up to (fqdn,ip,80) fqdn will be looked up to (fqdn,ip,80) ip will be looked up to (ip,ip,80) hostname:port will be looked up to (fqdn,ip,port) fqdn:port will be looked up to (fqdn,ip,port) ip:port will be looked up to (ip,ip,port) hostname:ip:port will be looked up to (fqdn,ip,port) fqdn:ip:port will be looked up to (fqdn,ip,port) ip1:ip2:port will be looked up to (ip1,ip2,port) ''' a = self._args tmp = what.split(':') if 1 == len(tmp): what = tmp[0] ip = None port = 80 elif 2 == len(tmp): what = tmp[0] ip = None port = tmp[1] elif 3 == len(tmp): what = tmp[0] ip = tmp[1] port = tmp[2] else: raise Exception('\'%s\' can have at most three kolons' % what) if ip: name = what else: data = socket.gethostbyname_ex(what) name = data[0] ips = data[2] if len(ips) > 1: raise Exception('\'%s\' resolves to multiple ips \'%s\'' % (name, ips)) ip = ips[0] self.verbose("Looked \'%s\' up to name:\'%s\', ip:\'%s\', port:\'%s\'" % (what, name, ip, port)) return (name, ip, port) def __do_node(self, f5): ''' Do stuff concerning nodes ''' a = self._args if not a.name: return lambda: f5().get_list() if a.sub == 'get': if a.sub2 == 'detail': def detail(f5): d = {} d['connection_limit'] = f5().get_connection_limit([a.name]) d['default_node_monitor'] = f5().get_default_node_monitor() d['description'] = f5().get_description([a.name]) d['dynamic_ratio'] = f5().get_dynamic_ratio_v2([a.name]) d['monitor_instance'] = f5().get_monitor_instance([a.name]) d['monitor_rule'] = f5().get_monitor_rule([a.name]) d['monitor_status'] = f5().get_monitor_status([a.name]) d['object_status'] = f5().get_object_status([a.name]) d['rate_limit'] = f5().get_rate_limit([a.name]) d['ratio'] = f5().get_ratio([a.name]) d['session_status'] = f5().get_session_status([a.name]) return d return lambda: detail(f5) if a.sub2 == 'status': return lambda: f5().get_monitor_status([a.name]) if a.sub2 == 'ratio': return lambda: f5().get_ratio([a.name]) if a.sub == 'set': if a.sub2 == 'ratio': ratio = a.sub3 return lambda: f5().set_ratio([a.name], [ratio]) elif a.sub == 'create': fqdn_or_ip, ip, _ = self.lookup(a.name) return lambda: f5().create([fqdn_or_ip], [ip], [0]) elif a.sub == 'delete': fqdn_or_ip, _, _ = self.lookup(a.name) return lambda: f5().delete_node_address([fqdn_or_ip]) def __do_monitor(self, f5): ''' Do stuff concerning monitor templates ''' a = self._args if not a.name: return lambda: f5().get_template_list() if a.sub == 'get': monitorname = a.sub3 if a.sub2 == 'desc': return lambda: f5().get_description([monitorname]) if a.sub2 == 'state': return lambda: f5().get_template_state([monitorname]) def __do_pool(self, f5): ''' Do stuff concerning pools ''' a = self._args if not a.name: return lambda: f5().get_list() if a.sub == 'get': if a.sub2 == 'detail': def detail(f5): d = {} d['allow_nat_state'] = f5().get_allow_nat_state([a.name]) d['allow_snat_state'] = f5().get_allow_snat_state([a.name]) d['description'] = f5().get_description([a.name]) d['lb_method'] = f5().get_lb_method([a.name]) d['member'] = f5().get_member_v2([a.name]) d['object_status'] = f5().get_object_status([a.name]) d['profile'] = f5().get_profile([a.name]) return d return lambda: detail(f5) elif a.sub2 == 'lbmethod': return lambda: f5().get_lb_method([a.name]) elif a.sub2 == 'members': return lambda: f5().get_member_v2([a.name]) elif a.sub2 == 'monitor': return lambda: f5().get_monitor_instance([a.name]) elif a.sub2 == 'status': return lambda: f5().get_object_status([a.name]) elif a.sub == 'set': if a.sub2 == 'lbmethod': lbmethod = a.sub3 return lambda: f5().set_lb_method([a.name], [lbmethod]) elif a.sub == 'create': poolmembers = [] method = 'LB_METHOD_ROUND_ROBIN' if a.sub2: for x in a.sub2.split(','): fqdn_or_ip, ip, port = self.lookup(x) pm = { 'address' : fqdn_or_ip, 'port' : port } poolmembers.append(pm) return lambda: f5().create_v2([a.name],[method],[poolmembers]) elif a.sub == 'delete': return lambda: f5().delete_pool([a.name]) elif a.sub == 'add': if a.sub2 == 'member': fqdn_or_ip, _, port = self.lookup(a.sub3) member = [{ 'address' : fqdn_or_ip, 'port' : port }] return lambda: f5().add_member_v2([a.name], [member]) elif a.sub2 == 'monitor': monitorname = a.sub3 rule = { 'type': 'MONITOR_RULE_TYPE_SINGLE', 'quorum': long(0), 'monitor_templates': [ monitorname ], } association = { 'pool_name': a.name, 'monitor_rule': rule } return lambda: f5().set_monitor_association([association]) elif a.sub == 'del': if a.sub2 == 'member': fqdn_or_ip, _, port = self.lookup(a.sub3) member = [{ 'address' : fqdn_or_ip, 'port' : port }] return lambda: f5().remove_member_v2([a.name], [member]) elif a.sub2 == 'monitors': # Removes all monitor associations, not just one return lambda: f5().remove_monitor_association([a.name]) def __do_poolmember(self, f5): ''' Do stuff concerning poolmebers ''' a = self._args member = a.sub if not member: return lambda: f5().get_ratio([a.name]) elif a.sub2 == 'get': if a.sub3 == 'ratio': return lambda: f5().get_ratio([a.name]) elif a.sub2 == 'set': if a.sub3 == 'ratio': ratio = a.sub4 _, ip, port = self.lookup(member) ipport = { "address": ip, "port": port } memberratio = { "member": ipport, "ratio": ratio } return lambda: f5().set_ratio([a.name], [[memberratio]]) def __do_vserver(self, f5): ''' Do stuff concerning virtual servers ''' a = self._args if not a.name: return lambda: f5().get_list() if a.a: name = a.name else: # Check for Pattern like /partition/foo-bar.example.com_443 m = re.match('^(.*)_(\d+)$', a.name) if m: fqdn_or_ip = m.group(1) port = m.group(2) _, ip, _ = self.lookup(fqdn_or_ip) else: fqdn_or_ip, ip, port = self.lookup(a.name) name = fqdn_or_ip + '_' + port if a.sub == 'get': if a.sub2 == 'detail': def detail(f5): d = {} d['actual_hardware_acceleration'] = f5().get_actual_hardware_acceleration([name]) d['auto_lasthop'] = f5().get_auto_lasthop([name]) d['bw_controller_policy'] = f5().get_bw_controller_policy([name]) d['clone_pool'] = f5().get_clone_pool([name]) d['connection_limit'] = f5().get_connection_limit([name]) d['default_pool_name'] = f5().get_default_pool_name([name]) d['description'] = f5().get_description([name]) d['destination'] = f5().get_destination_v2([name]) d['enabled_state'] = f5().get_enabled_state([name]) d['fallback_persistence_profile'] = f5().get_fallback_persistence_profile([name]) d['gtm_score'] = f5().get_gtm_score([name]) d['last_hop_pool'] = f5().get_last_hop_pool([name]) d['object_status'] = f5().get_object_status([name]) d['persistence_profile'] = f5().get_persistence_profile([name]) d['profile'] = f5().get_profile([name]) d['protocol'] = f5().get_protocol([name]) d['rule'] = f5().get_rule([name]) d['snat_pool'] = f5().get_snat_pool([name]) d['snat_type'] = f5().get_snat_type([name]) d['source_address'] = f5().get_source_address([name]) d['source_address_translation_lsn_pool'] = f5().get_source_address_translation_lsn_pool([name]) d['source_address_translation_snat_pool'] = f5().get_source_address_translation_snat_pool([name]) d['source_address_translation_type'] = f5().get_source_address_translation_type([name]) d['source_port_behavior'] = f5().get_source_port_behavior([name]) d['translate_address_state'] = f5().get_translate_address_state([name]) d['translate_port_state'] = f5().get_translate_port_state([name]) d['type'] = f5().get_type([name]) d['vlan'] = f5().get_vlan([name]) return d return lambda: detail(f5) elif a.sub2 == 'brief': def brief(f5): d = {} d['actual_hardware_acceleration'] = f5().get_actual_hardware_acceleration([name]) d['default_pool_name'] = f5().get_default_pool_name([name]) d['destination'] = f5().get_destination_v2([name]) d['enabled_state'] = f5().get_enabled_state([name]) d['object_status'] = f5().get_object_status([name]) d['persistence_profile'] = f5().get_persistence_profile([name]) d['profile'] = f5().get_profile([name]) d['protocol'] = f5().get_protocol([name]) d['translate_address_state'] = f5().get_translate_address_state([name]) d['translate_port_state'] = f5().get_translate_port_state([name]) d['type'] = f5().get_type([name]) return d return lambda: brief(f5) elif a.sub2 == 'status': return lambda: f5().get_object_status([name]) elif a.sub2 == 'persistence': return lambda: f5().get_persistence_profile([name]) elif a.sub2 == 'vlan': return lambda: f5().get_vlan([name]) elif a.sub2 == 'profile': return lambda: f5().get_profile([name]) elif a.sub2 == 'rule': return lambda: f5().get_rule([name]) elif a.sub == 'create': protocol = a.sub2 if a.sub2 else 'PROTOCOL_TCP' if a.sub3: profile = a.sub3 elif protocol == 'PROTOCOL_UDP': profile = 'udp' else: profile = 'tcp' poolname = a.sub4 netmask = a.sub5 if a.sub5 else '255.255.255.255' vserver = { 'name': name, 'address': ip, 'port': port, 'protocol': protocol, } resource = { 'type': 'RESOURCE_TYPE_POOL' } if poolname: resource['default_pool_name'] = poolname profile = { 'profile_context': 'PROFILE_CONTEXT_TYPE_ALL', 'profile_name': profile, } self.verbose("vserver:%s netmask:%s resource:%s, profile:%s" % (vserver, netmask, resource, profile)) def vserver_create(): f5().create([vserver], [netmask], [resource], [[profile]]) # Auto disable NAT and PAT if nPath if profile['profile_name'] == 'nPath': f5().set_translate_address_state([name], ['STATE_DISABLED']) f5().set_translate_port_state([name], ['STATE_DISABLED']) return lambda: vserver_create() elif a.sub == 'delete': return lambda: f5().delete_virtual_server([name]) elif a.sub == 'set': if a.sub2 == 'pool': poolname = a.sub3 return lambda: f5().set_default_pool_name([name], [poolname]) elif a.sub2 == 'nat': if a.sub3 == 'disabled': return lambda: f5().set_translate_address_state([name], ['STATE_DISABLED']) elif a.sub3 == 'enabled': return lambda: f5().set_translate_address_state([name], ['STATE_ENABLED']) elif a.sub2 == 'pat': if a.sub3 == 'disabled': return lambda: f5().set_translate_port_state([name], ['STATE_DISABLED']) elif a.sub3 == 'enabled': return lambda: f5().set_translate_port_state([name], ['STATE_ENABLED']) elif a.sub2 == 'snat': if a.sub3 == 'none': return lambda: f5().set_source_address_translation_none([name]) elif a.sub3 == 'automap': return lambda: f5().set_source_address_translation_automap([name]) elif a.sub2 == 'vlan': vlans = a.sub3.split(',') if a.sub3 else [] vlan_filter_list = { 'state': 'STATE_ENABLED', 'vlans': vlans } return lambda: f5().set_vlan([name], [vlan_filter_list]) elif a.sub == 'profile': profiles = { 'profile_context': a.sub4 if a.sub4 else 'PROFILE_CONTEXT_TYPE_CLIENT', 'profile_name': a.sub3, } if a.sub2 == 'add': return lambda: f5().add_profile([name], [[profiles]]) elif a.sub2 == 'remove': return lambda: f5().remove_profile([name], [[profiles]]) elif a.sub == 'rule': rules = { 'priority': a.sub4 if a.sub4 else '1', 'rule_name': a.sub3, } if a.sub2 == 'add': return lambda: f5().add_rule([name], [[rules]]) elif a.sub2 == 'remove': return lambda: f5().remove_rule([name], [[rules]]) def __do_vip(self, f5): ''' Do stuff concerning virtual addresses ''' a = self._args if not a.name: return lambda: f5().get_list() # Check for Pattern like /partition/foo-bar.example.com_443 m = re.match('^(.*)_(\d+)$', a.name) if m: fqdn_or_ip = m.group(1) _, ip, _ = self.lookup(fqdn_or_ip) else: _, ip, _ = self.lookup(a.name) name = ip # Do the actual stuff if a.sub == 'get': if a.sub2 == 'detail': def detail(f5): d = {} d['address'] = f5().get_address([name]) d['arp_state'] = f5().get_arp_state([name]) d['enabled_state'] = f5().get_enabled_state([name]) d['is_floating_state'] = f5().get_is_floating_state([name]) d['netmask'] = f5().get_netmask([name]) d['object_status'] = f5().get_object_status([name]) d['traffic_group'] = f5().get_traffic_group([name]) d['is_traffic_group_inherited'] = f5().is_traffic_group_inherited([name]) return d return lambda: detail(f5) elif a.sub2 == 'arp': return lambda: f5().get_arp-state([name]) elif a.sub2 == 'status': return lambda: f5().get_object_status([name]) elif a.sub2 == 'tgroup': return lambda: f5().get_traffic_group([name]) elif a.sub == 'create': netmask = a.sub2 return lambda: f5().create([name], [name], [netmask]) elif a.sub == 'delete': return lambda: f5().delete_virtual_address([name]) elif a.sub == 'set': if a.sub2 == 'arp': if a.sub3 == 'disabled': return lambda: f5().set_arp_state([name], ['STATE_DISABLED']) elif a.sub3 == 'enabled': return lambda: f5().set_arp_state([name], ['STATE_ENABLED']) elif a.sub2 == 'tgroup': tgroup = a.sub3 return lambda: f5().set_traffic_group([name], [tgroup]) def __do_profileclientssl(self, f5): ''' Do stuff concerning SSL client certs ''' a = self._args if not a.name: return lambda: f5().get_list() # Do the actual stuff if a.sub == 'get': if a.sub2 == 'detail': def detail(f5): d = {} d['ca_file'] = f5().get_ca_file_v2([a.name]) d['key_file'] = f5().get_key_file_v2([a.name]) d['certificate_file'] = f5().get_certificate_file_v2([a.name]) d['chain_file'] = f5().get_chain_file_v2([a.name]) d['client_certificate_ca_file'] = f5().get_client_certificate_ca_file_v2([a.name]) d['description'] = f5().get_description([a.name]) return d return lambda: detail(f5) if a.sub == 'create': key_path = a.sub2 if a.sub2 else a.name + '.key' certificate_path = a.sub3 if a.sub3 else a.name + '.crt' key_file = { 'value': key_path, 'default_flag': False, } certificate_file = { 'value': certificate_path, 'default_flag': False, } return lambda: f5().create_v2([a.name], [key_file], [certificate_file]) elif a.sub == 'delete': return lambda: f5().delete_profile([a.name]) def __do_vlan(self, f5): ''' Do stuff concerning VLANs ''' a = self._args if not a.name: return lambda: f5().get_list() if a.sub == 'get': if a.sub2 == 'detail': def detail(f5): d = {} d['auto_lasthop'] = f5().get_auto_lasthop([a.name]) d['vlan_id'] = f5().get_vlan_id([a.name]) d['member'] = f5().get_member([a.name]) d['mtu'] = f5().get_mtu([a.name]) return d return lambda: detail(f5) elif a.sub == 'create': if a.sub2 == 'tagged': vlanid = a.sub3 members = { 'member_name': a.sub4, 'member_type': 'MEMBER_TRUNK', 'tag_state': 'MEMBER_TAGGED', } return lambda: f5().create_v2([a.name], [vlanid], [[members]], ['STATE_DISABLED'], [10]) elif a.sub == 'delete': return lambda: f5().delete_vlan([a.name]) def __do_selfip(self, f5): ''' Do stuff concerning Self IPs ''' a = self._args if not a.name: return lambda: f5().get_list() if a.sub == 'get': if a.sub2 == 'detail': def detail(f5): d = {} d['address'] = f5().get_address([a.name]) d['description'] = f5().get_description([a.name]) d['floating_state'] = f5().get_floating_state([a.name]) d['traffic_group'] = f5().get_traffic_group([a.name]) return d return lambda: detail(f5) elif a.sub2 == 'tgroup': return lambda: f5().get_traffic_group([a.name]) elif a.sub2 == 'address': return lambda: f5().get_address([a.name]) if a.sub == 'set': if a.sub2 == 'tgroup': tgroup = a.sub3 return lambda: f5().set_traffic_group([a.name], [tgroup]) elif a.sub == 'create': _, ip, _ = self.lookup(a.name) netmask = a.sub2 vlanname = a.sub3 trafficgroup = a.sub4 if a.sub4 else 'traffic-group-local-only' # A floating traffic group will auto set floating state to ENABLED, regardless of the .create call. return lambda: f5().create([a.name], [vlanname], [ip], [netmask], [trafficgroup], ['STATE_DISABLED']) elif a.sub == 'delete': return lambda: f5().delete_self_ip([a.name]) def __do_folder(self, f5): ''' Do stuff concerning administrative Partitions''' a = self._args if not a.name: return lambda: f5().get_list() if a.sub == 'create': return lambda: f5().create([a.name]) elif a.sub == 'delete': return lambda: f5().delete_folder([a.name]) elif a.sub == 'get': if a.sub2 == 'detail': def detail(f5): d = {} d['description']= f5().get_description([a.name]) d['device_group']= f5().get_device_group([a.name]) d['traffic_group']= f5().get_traffic_group([a.name]) d['is_device_group_inherited']= f5().is_device_group_inherited([a.name]) d['is_traffic_group_inherited']= f5().is_traffic_group_inherited([a.name]) d['is_traffic_group_inherited']= f5().is_traffic_group_inherited([a.name]) return d return lambda: detail(f5) elif a.sub2 == 'dgroup': return lambda: f5().get_device_group([a.name]) elif a.sub2 == 'tgroup': return lambda: f5().get_traffic_group([a.name]) elif a.sub == 'set': if a.sub2 == 'dgroup': return lambda: f5().set_device_group([a.name], [a.sub3]) elif a.sub2 == 'tgroup': return lambda: f5().set_traffic_group([a.name], [a.sub3]) def __do_keycertificate(self, f5): ''' Do stuff concerning SSL keys and Certificates''' a = self._args if not a.name: # Somehow does not work with bugsuds 1.0.0 return lambda: f5().get_certificate_list_v2(0) def __do_tgroup(self, f5): ''' Do stuff concerning TrafficGroups''' a = self._args if not a.name: return lambda: f5().get_list() if a.sub == 'get': if a.sub2 == 'detail': def detail(f5): d = {} d['description'] = f5().get_description([a.name]) d['ha_order'] = f5().get_ha_order([a.name]) d['is_floating'] = f5().get_is_floating([a.name]) d['auto_failback_enabled_state'] = f5().get_auto_failback_enabled_state([a.name]) return d return lambda: detail(f5) elif a.sub2 == 'ha_order': return lambda: f5().get_ha_order([a.name]) elif a.sub == 'create': return lambda: f5().create([a.name]) elif a.sub == 'delete': return lambda: f5().delete_traffic_group([a.name]) elif a.sub == 'add': if a.sub2 == 'ha_order': orders = { 'device': a.sub3, 'order': a.sub4 } return lambda: f5().add_ha_order([a.name], [[orders]]) elif a.sub == 'remove': if a.sub2 == 'ha_order': orders = { 'device': a.sub3, 'order': a.sub4 } return lambda: f5().remove_ha_order([a.name], [[orders]]) elif a.sub2 == 'all_ha_orders': orders = { 'device': a.sub3, 'order': a.sub4 } return lambda: f5().remove_all_ha_orders([a.name]) def _lazy(self): ''' Get the lazy code block to be executed ''' a = self._args def namify(name): # Remove the /partition/ prefix, setting default partition after # login instead name = re.sub(self._folder, '', name) name = re.sub('^/+', '', name) return name if a.name: if isinstance(a.name, list): a.name = map(namify, a.name) else: a.name = namify(a.name) if a.what == 'node': return self.__do_node(lambda: self._f5.LocalLB.NodeAddressV2) elif a.what == 'monitor': return self.__do_monitor(lambda: self._f5.LocalLB.Monitor) elif a.what == 'pool': return self.__do_pool(lambda: self._f5.LocalLB.Pool) elif a.what == 'poolmember': return self.__do_poolmember(lambda: self._f5.LocalLB.PoolMember) elif a.what == 'vserver': return self.__do_vserver(lambda: self._f5.LocalLB.VirtualServer) elif a.what == 'vip': return self.__do_vip(lambda: self._f5.LocalLB.VirtualAddressV2) elif a.what == 'profileclientssl': return self.__do_profileclientssl(lambda: self._f5.LocalLB.ProfileClientSSL) elif a.what == 'vlan': return self.__do_vlan(lambda: self._f5.Networking.VLAN) elif a.what == 'selfip': return self.__do_selfip(lambda: self._f5.Networking.SelfIPV2) elif a.what == 'tgroup': return self.__do_tgroup(lambda: self._f5.Management.TrafficGroup) elif a.what == 'folder': return self.__do_folder(lambda: self._f5.Management.Folder) elif a.what == 'folder': return self.__do_folder(lambda: self._f5.Management.Folder) # Somehow does not work with bigsuds 1.0.0 # elif a.what == 'kc' or a.what == 'keycertificate': # return self.__do_keycertificate(lambda: self._f5.Management.KeyCertificate) def run(self): ''' Do the actual stuff. We are doning some lazy evaluation stuff here. The command line tool does not do anything with the slow F5 API until it is clear what to do and that there is no semantic or syntax error. ''' a = self._args # Inline bulk if a.name and ',' in a.name: a.name = a.name.split(',') lazy = self._lazy() if isfunction(lazy): if a.n: self.verbose(lazy) self.info('no-op', Fore.GREEN) else: self.verbose('Doing some stuf via the API, it may take a while') self.__login() self.out(lazy()) self.info('done', Fore.GREEN) else: self.print_synopsis() return 1 return 0 class FapiInteractive(FapiBase): ''' This is an interactive shell wrapper for Fapi ''' def __init__(self, args, parser): ''' Initialize the interactive Fapi shell''' global __interactive__ __interactive__ = True FapiBase.__init__(self, args) self._parser = parser def run(self): ''' Runs the interactive fapi shell ''' histfile = os.path.join(os.path.expanduser('~'), '.fapihist') try: readline.read_history_file(histfile) except IOError: pass self.print_version() while True: try: arguments = raw_input(__prompt__).split(' ') args = self._parser.parse(arguments) if args: Fapi(args).run() except EOFError: self.info("Good bye\n", Fore.GREEN) break return 0 if __name__ == '__main__': ''' The main function, here we will have Popcorn for free! ''' parser = ArgumentParser() args = parser.parse() if not args: sys.exit(0) fapi = FapiInteractive(args, parser) if args.i else Fapi(args) sys.exit(fapi.run()) # vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4