1 |
|
---|
2 | import re
|
---|
3 | import time
|
---|
4 | import csv
|
---|
5 |
|
---|
6 | from ....builtins import *
|
---|
7 |
|
---|
8 | from ...exceptions import *
|
---|
9 | from ...expressions import *
|
---|
10 | from ...autotype import *
|
---|
11 | from ...streams import *
|
---|
12 |
|
---|
13 | ##
|
---|
14 | ## Wrap a dictionary and provide access to it's keys as attributes.
|
---|
15 | ##
|
---|
16 | class DictAttr:
|
---|
17 |
|
---|
18 | def __init__(self, dict):
|
---|
19 | self.__dict__['_dict'] = dict
|
---|
20 |
|
---|
21 | def __getattr__(self, name):
|
---|
22 | if self._dict.has_key(name):
|
---|
23 | return self._dict[name]
|
---|
24 | else:
|
---|
25 | return None
|
---|
26 |
|
---|
27 | def __setattr__(self, name, value):
|
---|
28 | self._dict[name] = value
|
---|
29 |
|
---|
30 | def __getitem__(self, name):
|
---|
31 | if self._dict.has_key(name):
|
---|
32 | return self._dict[name]
|
---|
33 | else:
|
---|
34 | return None
|
---|
35 |
|
---|
36 | def __setitem__(self, name, value):
|
---|
37 | self._dict[name] = value
|
---|
38 |
|
---|
39 | def __str__(self):
|
---|
40 | return str(self._dict)
|
---|
41 |
|
---|
42 | #
|
---|
43 | # Here are some observations on the output of nsradmin:
|
---|
44 | #
|
---|
45 | # 1. Status messages at the top need to be skipped (Current query set, Display options, etc.,)
|
---|
46 | # 2. Attribute / value pairs are separated by a : in column 28.
|
---|
47 | # 3. Attribute / value pairs are terminated by a ; and can span 1 or more lines.
|
---|
48 | # 4. Attribute / value pairs will wrap long output lines with either a \ or ', ' (comma and space)
|
---|
49 | # as the last character before the newline.
|
---|
50 | # 5. An attribute can have one or more values. If multiple values are present, they are separated
|
---|
51 | # by commas and may or may not be contained in doublequotes (").
|
---|
52 | # 6. Multiple resource records are separated by a blank line.
|
---|
53 | #
|
---|
54 |
|
---|
55 | ##
|
---|
56 | ## ParseError
|
---|
57 | ##
|
---|
58 | class ParseError(Exception):
|
---|
59 | """Exception to be raised when data cannot be successfully parsed."""
|
---|
60 |
|
---|
61 |
|
---|
62 | ##
|
---|
63 | ## nsradmin_strip_header()
|
---|
64 | ##
|
---|
65 | ## Strip any leading data from nsradmin output that does not represent
|
---|
66 | ## valid NSR resource data.
|
---|
67 | ##
|
---|
68 | ## EXAMPLE
|
---|
69 | ##
|
---|
70 | ## Current query set
|
---|
71 | ## Hidden display option turned on
|
---|
72 | ##
|
---|
73 | ## Display options:
|
---|
74 | ## Dynamic: Off;
|
---|
75 | ## Hidden: On;
|
---|
76 | ## Resource ID: Off;
|
---|
77 | ## Dynamic display option turned on
|
---|
78 | ##
|
---|
79 | ## Display options:
|
---|
80 | ## Dynamic: On;
|
---|
81 | ## Hidden: On;
|
---|
82 | ## Resource ID: Off;
|
---|
83 | ##
|
---|
84 | def nsradmin_strip_header(lines):
|
---|
85 | i = 0
|
---|
86 | for line in lines:
|
---|
87 | # Read until we encounter a : at position 28 in the line
|
---|
88 | if len(line) >= 28 and line[28] == ':':
|
---|
89 | return lines[i:]
|
---|
90 | i += 1
|
---|
91 | raise ParseError
|
---|
92 |
|
---|
93 |
|
---|
94 | ##
|
---|
95 | ## Return true if data is valid nsradmin output
|
---|
96 | ##
|
---|
97 | def nsradmin_validate(lines):
|
---|
98 |
|
---|
99 | #
|
---|
100 | # Regular expressions that, if matched signify a fatal error in the data.
|
---|
101 | #
|
---|
102 | expressions = [
|
---|
103 | r'^Remote system error$',
|
---|
104 | r'^No resources found for query:',
|
---|
105 | r'^nsradmin: lost connection to server:',
|
---|
106 | r'^nsradmin: Failed to rebind to the server',
|
---|
107 | r'^nsradmin: RPC error:',
|
---|
108 | r'^nsradmin: There does not appear to be a NetWorker server running on',
|
---|
109 | ]
|
---|
110 |
|
---|
111 | compiled_expressions = [ re.compile(regexp) for regexp in expressions ]
|
---|
112 |
|
---|
113 | count = 0 # Count of key / value pairs
|
---|
114 |
|
---|
115 | for line in lines:
|
---|
116 | for regexp in compiled_expressions:
|
---|
117 | if regexp.search(line):
|
---|
118 | return False
|
---|
119 | if len(line) > 28 and line[28] == ':':
|
---|
120 | count += 1
|
---|
121 | if count > 0:
|
---|
122 | return True
|
---|
123 | else:
|
---|
124 | return False
|
---|
125 |
|
---|
126 |
|
---|
127 | ##
|
---|
128 | ## nsradmin_unwrap()
|
---|
129 | ##
|
---|
130 | ## Return nsradmin resource data with all wrapped lines unwrapped, resulting in
|
---|
131 | ## one key / value pair per line.
|
---|
132 | ##
|
---|
133 | def nsradmin_unwrap(lines):
|
---|
134 | i = 0
|
---|
135 | j = len(lines)
|
---|
136 | unwrapped_lines = []
|
---|
137 | while( i < j ):
|
---|
138 | line = lines[i]
|
---|
139 | line = line[:-1]
|
---|
140 | #
|
---|
141 | # Unwrap following lines if this line is terminated by a \
|
---|
142 | #
|
---|
143 | if re.compile(r'\\$').search(line):
|
---|
144 | line = re.compile(r'\\$').sub('', line)
|
---|
145 | while(1):
|
---|
146 | i += 1
|
---|
147 | line += lines[i][:-1].lstrip()
|
---|
148 | if re.compile(r';$').search(line):
|
---|
149 | break
|
---|
150 | else:
|
---|
151 | line = re.compile(r'\\$').sub('', line)
|
---|
152 | #
|
---|
153 | # Unwrap following lines if this line is terminated by ', '
|
---|
154 | #
|
---|
155 | elif re.compile(r', $').search(line):
|
---|
156 | while(1):
|
---|
157 | i += 1
|
---|
158 | try:
|
---|
159 | line += lines[i][:-1].lstrip()
|
---|
160 | except:
|
---|
161 | print line
|
---|
162 | if re.compile(r';$').search(line):
|
---|
163 | break
|
---|
164 | unwrapped_lines.append(line)
|
---|
165 | i += 1
|
---|
166 | return unwrapped_lines
|
---|
167 |
|
---|
168 |
|
---|
169 | ##
|
---|
170 | ## nsradmin_dict()
|
---|
171 | ##
|
---|
172 | ## Return the contents of an uwrapped nsradmin resource record as a dictionary
|
---|
173 | ## with spaces and slashes in the attribute names replaced by underscores for use
|
---|
174 | ## as dictionary keys.
|
---|
175 | ##
|
---|
176 | def nsradmin_dict(lines):
|
---|
177 | dict = {}
|
---|
178 | count = 0 # Count of key / value pairs found
|
---|
179 | for line in lines:
|
---|
180 | if re.compile(r'^\s+[a-zA-Z/ ]+:.*;$').match(line):
|
---|
181 | count += 1
|
---|
182 | line = re.compile(r';$').sub('', line)
|
---|
183 | key = line[:28]
|
---|
184 | key = re.compile(r'^\s+').sub('', key)
|
---|
185 | key = key.lower()
|
---|
186 | key = key.replace(' ','_')
|
---|
187 | key = key.replace('/','_')
|
---|
188 | value = line[30:]
|
---|
189 | dict[key] = value
|
---|
190 | if count == 0:
|
---|
191 | raise ParseError
|
---|
192 | else:
|
---|
193 | return dict
|
---|
194 |
|
---|
195 |
|
---|
196 | ##
|
---|
197 | ## Object Oriented Wrapper for a Networker Resource
|
---|
198 | ##
|
---|
199 | class NsrResource(DictAttr):
|
---|
200 |
|
---|
201 | def __init__(self, lines):
|
---|
202 |
|
---|
203 | dict = nsradmin_dict(nsradmin_unwrap(nsradmin_strip_header(lines)))
|
---|
204 | newdict = {}
|
---|
205 | keys = dict.keys()
|
---|
206 | keys.sort()
|
---|
207 | resource_type = None
|
---|
208 |
|
---|
209 | #
|
---|
210 | # Keys that may be quoted or contain commas, but which should still be stored
|
---|
211 | # as single values.
|
---|
212 | #
|
---|
213 | single_keys = {
|
---|
214 | 'nsr' : [ 'message', 'save_totals', 'totals_since' ],
|
---|
215 | 'nsr client' : [ ],
|
---|
216 | 'nsr device' : [ 'name', 'date_last_cleaned', 'message', 'volume_expiration' ],
|
---|
217 | 'nsr directive' : [ ],
|
---|
218 | 'nsr group' : [ 'completion', 'interval', 'last_start', 'start_time' ],
|
---|
219 | 'nsr jukebox' : [ 'event_message' ],
|
---|
220 | 'nsr label' : [ ],
|
---|
221 | 'nsr license' : [ 'checksum' ],
|
---|
222 | 'nsr notification' : [ ],
|
---|
223 | 'nsr policy' : [ ],
|
---|
224 | 'nsr pool' : [ ],
|
---|
225 | 'nsr schedule' : [ ],
|
---|
226 | 'nsr stage' : [ ],
|
---|
227 | }
|
---|
228 |
|
---|
229 | #
|
---|
230 | # Keys that should always be treated as lists of one or more values.
|
---|
231 | #
|
---|
232 | list_keys = {
|
---|
233 | 'nsr' : [ 'onc_transport', 'types_created', 'sequence_number', 'server_message_category',
|
---|
234 | 'server_message_priority', 'server_message_source', 'message_list', 'server_message',
|
---|
235 | 'privileges', 'administrator', 'member_of' ],
|
---|
236 | 'nsr client' : [ 'onc_transport', 'aliases' ],
|
---|
237 | 'nsr device' : [ 'onc_transport' ],
|
---|
238 | 'nsr directive' : [ 'onc_transport' ],
|
---|
239 | 'nsr group' : [ 'onc_transport' ],
|
---|
240 | 'nsr jukebox' : [ 'onc_transport', 'devices', 'volumes', 'volume_ids', 'jukebox_features', 'physical_slots',
|
---|
241 | 'stl_barcodes', 'messages', 'stl_device_reservation', 'load_enabled', 'administrator' ],
|
---|
242 | 'nsr label' : [ 'onc_transport' ],
|
---|
243 | 'nsr license' : [ 'onc_transport' ],
|
---|
244 | 'nsr notification' : [ 'onc_transport', 'event', 'priority' ],
|
---|
245 | 'nsr policy' : [ 'onc_transport' ],
|
---|
246 | 'nsr pool' : [ 'onc_transport' ],
|
---|
247 | 'nsr schedule' : [ 'onc_transport' ],
|
---|
248 | 'nsr stage' : [ 'onc_transport' ],
|
---|
249 | }
|
---|
250 |
|
---|
251 | #
|
---|
252 | # If the data contains a type field, set the resource_type variable so we can use
|
---|
253 | # it to handle certain keys as lists or dictionaries depending on the resource
|
---|
254 | # type.
|
---|
255 | #
|
---|
256 | if dict.has_key('type'):
|
---|
257 | resource_type = dict['type'].lower()
|
---|
258 |
|
---|
259 | #
|
---|
260 | # Iterate through the rest of the key / value pairs, storing them appropriately
|
---|
261 | # in the object's data structure.
|
---|
262 | #
|
---|
263 | for key in keys:
|
---|
264 | value = dict[key]
|
---|
265 | #
|
---|
266 | # If the key is to be treated as a single value, do so.
|
---|
267 | #
|
---|
268 | if single_keys.has_key(resource_type) and key in single_keys[resource_type]:
|
---|
269 | value = re.compile(r'^"').sub('', value)
|
---|
270 | value = re.compile(r'"$').sub('', value)
|
---|
271 | value = value.lstrip()
|
---|
272 | value = value.rstrip()
|
---|
273 | value = value.replace('\\\\','\\') # Unescape backslashes
|
---|
274 | if re.compile(r'^\d+$').match(value):
|
---|
275 | value = int(value)
|
---|
276 | newdict[key] = value
|
---|
277 | #
|
---|
278 | # If the key is to be treated as a list, do so.
|
---|
279 | #
|
---|
280 | elif list_keys.has_key(resource_type) and key in list_keys[resource_type]:
|
---|
281 | list = []
|
---|
282 | for item in value.split(', '):
|
---|
283 | item = re.compile(r'^"').sub('', item)
|
---|
284 | item = re.compile(r'"$').sub('', item)
|
---|
285 | item = item.lstrip()
|
---|
286 | item = item.rstrip()
|
---|
287 | item = item.replace('\\\\','\\') # Unescape backslashes
|
---|
288 | if re.compile(r'^\d+$').match(item):
|
---|
289 | item = int(item)
|
---|
290 | list.append(item)
|
---|
291 | newdict[key] = list
|
---|
292 | #
|
---|
293 | # If the key consists of one or more attribute = value pairs, store it as a dictionary
|
---|
294 | # (wrapped using DictAttr).
|
---|
295 | #
|
---|
296 | elif re.compile('([a-zA-Z0-9_:" ]+=[a-zA-Z0-9_:" ]+, )+').search(value):
|
---|
297 | csvdict = {}
|
---|
298 | for pairs in value.split(', '):
|
---|
299 | try:
|
---|
300 | (sname, svalue) = pairs.split('=')
|
---|
301 | except:
|
---|
302 | print key, value
|
---|
303 | sname = sname.lstrip()
|
---|
304 | sname = sname.rstrip()
|
---|
305 | sname = sname.replace(' ','_')
|
---|
306 | sname = sname.replace('(','')
|
---|
307 | sname = sname.replace(')','')
|
---|
308 | sname = sname.lower()
|
---|
309 | svalue = svalue.lstrip()
|
---|
310 | svalue = svalue.rstrip()
|
---|
311 | sname = re.compile(r'^"').sub('', sname)
|
---|
312 | sname = re.compile(r'"$').sub('', sname)
|
---|
313 | svalue = re.compile(r'^"').sub('', svalue)
|
---|
314 | svalue = re.compile(r'"$').sub('', svalue)
|
---|
315 | if re.compile(r'^\d+$').match(svalue):
|
---|
316 | svalue = int(svalue)
|
---|
317 | csvdict[sname] = svalue
|
---|
318 | newdict[key] = DictAttr(csvdict)
|
---|
319 | #
|
---|
320 | # If the key is quoted, assume this is potentially a list of quoted strings and
|
---|
321 | # store the contents in a list, even if there is just one value.
|
---|
322 | #
|
---|
323 | elif re.compile('"[^"]*"').search(value):
|
---|
324 | list = []
|
---|
325 | matches = re.compile('"[^"]*"').findall(value)
|
---|
326 | for match in matches:
|
---|
327 | match = re.compile(r'^"').sub('', match)
|
---|
328 | match = re.compile(r'"$').sub('', match)
|
---|
329 | match = match.lstrip()
|
---|
330 | match = match.rstrip()
|
---|
331 | match = match.replace('\\\\','\\') # Unescape backslashes
|
---|
332 | match = match.replace('\\-','-') # Remove backslashes before hyphens
|
---|
333 | match = match.replace('-\\','-') # Remove backslashes after hyphens
|
---|
334 | if re.compile(r'^\d+$').match(match):
|
---|
335 | match = int(match)
|
---|
336 | list.append(match)
|
---|
337 | newdict[key] = list
|
---|
338 | #
|
---|
339 | # If no special treatment is required, just store the value.
|
---|
340 | #
|
---|
341 | else:
|
---|
342 | value = re.compile(r'^"').sub('', value)
|
---|
343 | value = re.compile(r'"$').sub('', value)
|
---|
344 | value = value.lstrip()
|
---|
345 | value = value.rstrip()
|
---|
346 | value = value.replace('\\\\','\\') # Unescape backslashes
|
---|
347 | if re.compile(r'^\d+$').match(value):
|
---|
348 | value = int(value)
|
---|
349 | newdict[key] = value
|
---|
350 | #
|
---|
351 | # Initialize the NsrResource object by calling the parent's init method.
|
---|
352 | #
|
---|
353 | DictAttr.__init__(self, newdict)
|
---|
354 |
|
---|
355 |
|
---|
356 | ##
|
---|
357 | ## Split an nsradmin text stream into a list of unique resource records
|
---|
358 | ##
|
---|
359 | def nsradmin_split(lines):
|
---|
360 | i = 0
|
---|
361 | j = len(lines)
|
---|
362 | records = []
|
---|
363 | record = []
|
---|
364 | #
|
---|
365 | # Skip leading garbage
|
---|
366 | #
|
---|
367 | while i < j:
|
---|
368 | line = lines[i]
|
---|
369 | if len(line) >= 28 and line[28] == ':':
|
---|
370 | break
|
---|
371 | else:
|
---|
372 | i += 1
|
---|
373 | #
|
---|
374 | # Process resource records
|
---|
375 | #
|
---|
376 | previous_terminator = None
|
---|
377 | while i < j:
|
---|
378 | line = lines[i]
|
---|
379 | #
|
---|
380 | # If a new record starts here, add the previous to the return set before continuing
|
---|
381 | #
|
---|
382 | # (A blank line by itself is not enough to delimit a new record. The previous line must
|
---|
383 | # have been terminated by a semi-colon.)
|
---|
384 | #
|
---|
385 | if re.compile('^$').match(line) and previous_terminator == ';':
|
---|
386 | records.append(record)
|
---|
387 | record = []
|
---|
388 | else:
|
---|
389 | record.append(line)
|
---|
390 | i += 1
|
---|
391 | if len(line) > 1:
|
---|
392 | previous_terminator = line[-2]
|
---|
393 | #
|
---|
394 | # Add the final record to the list and return
|
---|
395 | #
|
---|
396 | records.append(record)
|
---|
397 | return records
|
---|
398 |
|
---|
399 |
|
---|