import re import time import csv from ....builtins import * from ...exceptions import * from ...expressions import * from ...autotype import * from ...streams import * ## ## Wrap a dictionary and provide access to it's keys as attributes. ## class DictAttr: def __init__(self, dict): self.__dict__['_dict'] = dict def __getattr__(self, name): if self._dict.has_key(name): return self._dict[name] else: return None def __setattr__(self, name, value): self._dict[name] = value def __getitem__(self, name): if self._dict.has_key(name): return self._dict[name] else: return None def __setitem__(self, name, value): self._dict[name] = value def __str__(self): return str(self._dict) # # Here are some observations on the output of nsradmin: # # 1. Status messages at the top need to be skipped (Current query set, Display options, etc.,) # 2. Attribute / value pairs are separated by a : in column 28. # 3. Attribute / value pairs are terminated by a ; and can span 1 or more lines. # 4. Attribute / value pairs will wrap long output lines with either a \ or ', ' (comma and space) # as the last character before the newline. # 5. An attribute can have one or more values. If multiple values are present, they are separated # by commas and may or may not be contained in doublequotes ("). # 6. Multiple resource records are separated by a blank line. # ## ## ParseError ## class ParseError(Exception): """Exception to be raised when data cannot be successfully parsed.""" ## ## nsradmin_strip_header() ## ## Strip any leading data from nsradmin output that does not represent ## valid NSR resource data. ## ## EXAMPLE ## ## Current query set ## Hidden display option turned on ## ## Display options: ## Dynamic: Off; ## Hidden: On; ## Resource ID: Off; ## Dynamic display option turned on ## ## Display options: ## Dynamic: On; ## Hidden: On; ## Resource ID: Off; ## def nsradmin_strip_header(lines): i = 0 for line in lines: # Read until we encounter a : at position 28 in the line if len(line) >= 28 and line[28] == ':': return lines[i:] i += 1 raise ParseError ## ## Return true if data is valid nsradmin output ## def nsradmin_validate(lines): # # Regular expressions that, if matched signify a fatal error in the data. # expressions = [ r'^Remote system error$', r'^No resources found for query:', r'^nsradmin: lost connection to server:', r'^nsradmin: Failed to rebind to the server', r'^nsradmin: RPC error:', r'^nsradmin: There does not appear to be a NetWorker server running on', ] compiled_expressions = [ re.compile(regexp) for regexp in expressions ] count = 0 # Count of key / value pairs for line in lines: for regexp in compiled_expressions: if regexp.search(line): return False if len(line) > 28 and line[28] == ':': count += 1 if count > 0: return True else: return False ## ## nsradmin_unwrap() ## ## Return nsradmin resource data with all wrapped lines unwrapped, resulting in ## one key / value pair per line. ## def nsradmin_unwrap(lines): i = 0 j = len(lines) unwrapped_lines = [] while( i < j ): line = lines[i] line = line[:-1] # # Unwrap following lines if this line is terminated by a \ # if re.compile(r'\\$').search(line): line = re.compile(r'\\$').sub('', line) while(1): i += 1 line += lines[i][:-1].lstrip() if re.compile(r';$').search(line): break else: line = re.compile(r'\\$').sub('', line) # # Unwrap following lines if this line is terminated by ', ' # elif re.compile(r', $').search(line): while(1): i += 1 try: line += lines[i][:-1].lstrip() except: print line if re.compile(r';$').search(line): break unwrapped_lines.append(line) i += 1 return unwrapped_lines ## ## nsradmin_dict() ## ## Return the contents of an uwrapped nsradmin resource record as a dictionary ## with spaces and slashes in the attribute names replaced by underscores for use ## as dictionary keys. ## def nsradmin_dict(lines): dict = {} count = 0 # Count of key / value pairs found for line in lines: if re.compile(r'^\s+[a-zA-Z/ ]+:.*;$').match(line): count += 1 line = re.compile(r';$').sub('', line) key = line[:28] key = re.compile(r'^\s+').sub('', key) key = key.lower() key = key.replace(' ','_') key = key.replace('/','_') value = line[30:] dict[key] = value if count == 0: raise ParseError else: return dict ## ## Object Oriented Wrapper for a Networker Resource ## class NsrResource(DictAttr): def __init__(self, lines): dict = nsradmin_dict(nsradmin_unwrap(nsradmin_strip_header(lines))) newdict = {} keys = dict.keys() keys.sort() resource_type = None # # Keys that may be quoted or contain commas, but which should still be stored # as single values. # single_keys = { 'nsr' : [ 'message', 'save_totals', 'totals_since' ], 'nsr client' : [ ], 'nsr device' : [ 'name', 'date_last_cleaned', 'message', 'volume_expiration' ], 'nsr directive' : [ ], 'nsr group' : [ 'completion', 'interval', 'last_start', 'start_time' ], 'nsr jukebox' : [ 'event_message' ], 'nsr label' : [ ], 'nsr license' : [ 'checksum' ], 'nsr notification' : [ ], 'nsr policy' : [ ], 'nsr pool' : [ ], 'nsr schedule' : [ ], 'nsr stage' : [ ], } # # Keys that should always be treated as lists of one or more values. # list_keys = { 'nsr' : [ 'onc_transport', 'types_created', 'sequence_number', 'server_message_category', 'server_message_priority', 'server_message_source', 'message_list', 'server_message', 'privileges', 'administrator', 'member_of' ], 'nsr client' : [ 'onc_transport', 'aliases' ], 'nsr device' : [ 'onc_transport' ], 'nsr directive' : [ 'onc_transport' ], 'nsr group' : [ 'onc_transport' ], 'nsr jukebox' : [ 'onc_transport', 'devices', 'volumes', 'volume_ids', 'jukebox_features', 'physical_slots', 'stl_barcodes', 'messages', 'stl_device_reservation', 'load_enabled', 'administrator' ], 'nsr label' : [ 'onc_transport' ], 'nsr license' : [ 'onc_transport' ], 'nsr notification' : [ 'onc_transport', 'event', 'priority' ], 'nsr policy' : [ 'onc_transport' ], 'nsr pool' : [ 'onc_transport' ], 'nsr schedule' : [ 'onc_transport' ], 'nsr stage' : [ 'onc_transport' ], } # # If the data contains a type field, set the resource_type variable so we can use # it to handle certain keys as lists or dictionaries depending on the resource # type. # if dict.has_key('type'): resource_type = dict['type'].lower() # # Iterate through the rest of the key / value pairs, storing them appropriately # in the object's data structure. # for key in keys: value = dict[key] # # If the key is to be treated as a single value, do so. # if single_keys.has_key(resource_type) and key in single_keys[resource_type]: value = re.compile(r'^"').sub('', value) value = re.compile(r'"$').sub('', value) value = value.lstrip() value = value.rstrip() value = value.replace('\\\\','\\') # Unescape backslashes if re.compile(r'^\d+$').match(value): value = int(value) newdict[key] = value # # If the key is to be treated as a list, do so. # elif list_keys.has_key(resource_type) and key in list_keys[resource_type]: list = [] for item in value.split(', '): item = re.compile(r'^"').sub('', item) item = re.compile(r'"$').sub('', item) item = item.lstrip() item = item.rstrip() item = item.replace('\\\\','\\') # Unescape backslashes if re.compile(r'^\d+$').match(item): item = int(item) list.append(item) newdict[key] = list # # If the key consists of one or more attribute = value pairs, store it as a dictionary # (wrapped using DictAttr). # elif re.compile('([a-zA-Z0-9_:" ]+=[a-zA-Z0-9_:" ]+, )+').search(value): csvdict = {} for pairs in value.split(', '): try: (sname, svalue) = pairs.split('=') except: print key, value sname = sname.lstrip() sname = sname.rstrip() sname = sname.replace(' ','_') sname = sname.replace('(','') sname = sname.replace(')','') sname = sname.lower() svalue = svalue.lstrip() svalue = svalue.rstrip() sname = re.compile(r'^"').sub('', sname) sname = re.compile(r'"$').sub('', sname) svalue = re.compile(r'^"').sub('', svalue) svalue = re.compile(r'"$').sub('', svalue) if re.compile(r'^\d+$').match(svalue): svalue = int(svalue) csvdict[sname] = svalue newdict[key] = DictAttr(csvdict) # # If the key is quoted, assume this is potentially a list of quoted strings and # store the contents in a list, even if there is just one value. # elif re.compile('"[^"]*"').search(value): list = [] matches = re.compile('"[^"]*"').findall(value) for match in matches: match = re.compile(r'^"').sub('', match) match = re.compile(r'"$').sub('', match) match = match.lstrip() match = match.rstrip() match = match.replace('\\\\','\\') # Unescape backslashes match = match.replace('\\-','-') # Remove backslashes before hyphens match = match.replace('-\\','-') # Remove backslashes after hyphens if re.compile(r'^\d+$').match(match): match = int(match) list.append(match) newdict[key] = list # # If no special treatment is required, just store the value. # else: value = re.compile(r'^"').sub('', value) value = re.compile(r'"$').sub('', value) value = value.lstrip() value = value.rstrip() value = value.replace('\\\\','\\') # Unescape backslashes if re.compile(r'^\d+$').match(value): value = int(value) newdict[key] = value # # Initialize the NsrResource object by calling the parent's init method. # DictAttr.__init__(self, newdict) ## ## Split an nsradmin text stream into a list of unique resource records ## def nsradmin_split(lines): i = 0 j = len(lines) records = [] record = [] # # Skip leading garbage # while i < j: line = lines[i] if len(line) >= 28 and line[28] == ':': break else: i += 1 # # Process resource records # previous_terminator = None while i < j: line = lines[i] # # If a new record starts here, add the previous to the return set before continuing # # (A blank line by itself is not enough to delimit a new record. The previous line must # have been terminated by a semi-colon.) # if re.compile('^$').match(line) and previous_terminator == ';': records.append(record) record = [] else: record.append(line) i += 1 if len(line) > 1: previous_terminator = line[-2] # # Add the final record to the list and return # records.append(record) return records