Skip to content

Commit dab6fab

Browse files
author
David Noble
committed
Bug fixes and feature enhancements targeting the next Python SDK release.
1 parent 39cfb45 commit dab6fab

File tree

12 files changed

+173
-82
lines changed

12 files changed

+173
-82
lines changed

splunklib/ordereddict.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
from UserDict import DictMixin
2424

25+
2526
class OrderedDict(dict, DictMixin):
2627

2728
def __init__(self, *args, **kwds):

splunklib/searchcommands/__init__.py

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@
157157
import os
158158
msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
159159

160+
160161
def dispatch(command_class, argv=sys.argv, input_file=sys.stdin, output_file=
161162
sys.stdout, module_name=None):
162163
""" Instantiates and executes a search command class
@@ -219,14 +220,6 @@ def stream(records):
219220
Unconditionally dispatches :code:`SomeStreamingCommand`.
220221
221222
"""
222-
if module_name is not None and module_name != '__main__':
223-
return
224-
225-
try:
223+
if module_name is None or module_name == '__main__':
226224
command_class().process(argv, input_file, output_file)
227-
except:
228-
import logging
229-
import traceback
230-
logging.fatal(traceback.format_exc())
231-
232225
return

splunklib/searchcommands/logging.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from logging.config import fileConfig
1818
from logging import getLogger, root, StreamHandler
1919
import os
20+
import sys
2021

2122

2223
def configure(name, path=None):
@@ -70,7 +71,7 @@ def configure(name, path=None):
7071
.. _ConfigParser format: http://goo.gl/K6edZ8
7172
7273
"""
73-
app_directory = os.path.dirname(os.getcwd())
74+
app_directory = os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0])))
7475

7576
if path is None:
7677
probing_path = [
@@ -101,9 +102,13 @@ def configure(name, path=None):
101102
if path is not None:
102103
working_directory = os.getcwd()
103104
os.chdir(app_directory)
105+
try:
106+
splunk_home = os.path.normpath(os.path.join(working_directory, os.environ['SPLUNK_HOME']))
107+
except KeyError:
108+
splunk_home = working_directory # reasonable in debug scenarios
104109
try:
105110
path = os.path.abspath(path)
106-
fileConfig(path)
111+
fileConfig(path, {'SPLUNK_HOME': splunk_home})
107112
finally:
108113
os.chdir(working_directory)
109114

splunklib/searchcommands/reporting_command.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from . search_command_internals import ConfigurationSettingsType
1818
from . streaming_command import StreamingCommand
1919
from . search_command import SearchCommand
20-
from . import csv
20+
from . import splunk_csv
2121

2222

2323
class ReportingCommand(SearchCommand):
@@ -88,7 +88,7 @@ def _prepare(self, argv, input_file):
8888
if input_file is None:
8989
reader = None
9090
else:
91-
reader = csv.DictReader(input_file)
91+
reader = splunk_csv.DictReader(input_file)
9292
return ConfigurationSettings, operation, argv, reader
9393

9494
#endregion

splunklib/searchcommands/search_command.py

Lines changed: 75 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -23,20 +23,19 @@
2323
except ImportError:
2424
from ordereddict import OrderedDict # python 2.6
2525

26-
from inspect import getmembers
2726
from logging import _levelNames, getLevelName
27+
from inspect import getmembers
2828
from os import path
2929
from sys import argv, exit, stdin, stdout
3030
from urlparse import urlsplit
3131
from xml.etree import ElementTree
3232

3333
# Relative imports
3434

35-
from . import csv, logging
35+
from . import logging, splunk_csv
3636
from .decorators import Option
3737
from .validators import Boolean, Fieldname
38-
from .search_command_internals import InputHeader, MessagesHeader, \
39-
SearchCommandParser
38+
from .search_command_internals import InputHeader, MessagesHeader, SearchCommandParser
4039

4140

4241
class SearchCommand(object):
@@ -48,8 +47,7 @@ def __init__(self):
4847

4948
# Variables that may be used, but not altered by derived classes
5049

51-
self.logger, self._logging_configuration = logging.configure(
52-
type(self).__name__)
50+
self.logger, self._logging_configuration = logging.configure(type(self).__name__)
5351
self.input_header = InputHeader()
5452
self.messages = MessagesHeader()
5553

@@ -59,6 +57,7 @@ def __init__(self):
5957
self._configuration = None
6058
self._fieldnames = None
6159
self._option_view = None
60+
self._output_file = None
6261
self._search_results_info = None
6362
self._service = None
6463

@@ -219,7 +218,7 @@ def convert_value(field, value):
219218
fields = [convert_field(x) for x in reader.next()]
220219
values = [convert_value(f, v) for f, v in zip(fields, reader.next())]
221220

222-
search_results_info_type = namedtuple("SearchResultsInfo", fields)
221+
search_results_info_type = namedtuple('SearchResultsInfo', fields)
223222
self._search_results_info = search_results_info_type._make(values)
224223

225224
return self._search_results_info
@@ -256,22 +255,22 @@ def service(self):
256255
if info is None:
257256
return None
258257

259-
_, netloc, _, _, _ = urlsplit(
260-
info.splunkd_uri, info.splunkd_protocol, allow_fragments=False)
261-
262-
splunkd_host, _ = netloc.split(':')
258+
splunkd = urlsplit(info.splunkd_uri, info.splunkd_protocol, allow_fragments=False)
263259

264260
self._service = Service(
265-
scheme=info.splunkd_protocol, host=splunkd_host,
266-
port=info.splunkd_port, token=info.auth_token,
267-
app=info.ppc_app)
261+
scheme=splunkd.scheme, host=splunkd.hostname, port=splunkd.port, token=info.auth_token, app=info.ppc_app)
268262

269263
return self._service
270264

271265
#endregion
272266

273267
#region Methods
274268

269+
def error_exit(self, error):
270+
self.logger.error('Abnormal exit: ' + error)
271+
self.write_error(error)
272+
exit(1)
273+
275274
def process(self, args=argv, input_file=stdin, output_file=stdout):
276275
""" Processes search results as specified by command arguments.
277276
@@ -280,64 +279,70 @@ def process(self, args=argv, input_file=stdin, output_file=stdout):
280279
:param output_file: Pipeline output file
281280
282281
"""
283-
self.logger.debug('%s arguments: %s' % (type(self).__name__, args))
282+
self.logger.debug(u'%s arguments: %s', type(self).__name__, args)
284283
self._configuration = None
284+
self._output_file = output_file
285285

286286
try:
287287
if len(args) >= 2 and args[1] == '__GETINFO__':
288288

289-
ConfigurationSettings, operation, args, reader = self._prepare(
290-
args, input_file=None)
291-
289+
ConfigurationSettings, operation, args, reader = self._prepare(args, input_file=None)
292290
self.parser.parse(args, self)
293-
294291
self._configuration = ConfigurationSettings(self)
295-
296-
writer = csv.DictWriter(
297-
output_file, self, self.configuration.keys(), mv_delimiter=',')
292+
writer = splunk_csv.DictWriter(output_file, self, self.configuration.keys(), mv_delimiter=',')
298293
writer.writerow(self.configuration.items())
299294

300295
elif len(args) >= 2 and args[1] == '__EXECUTE__':
301296

302297
self.input_header.read(input_file)
303-
304-
ConfigurationSettings, operation, args, reader = self._prepare(
305-
args, input_file)
306-
298+
ConfigurationSettings, operation, args, reader = self._prepare(args, input_file)
307299
self.parser.parse(args, self)
308-
309300
self._configuration = ConfigurationSettings(self)
310301

311302
if self.show_configuration:
312303
self.messages.append(
313304
'info_message', '%s command configuration settings: %s'
314305
% (self.name, self._configuration))
315306

316-
writer = csv.DictWriter(output_file, self)
307+
writer = splunk_csv.DictWriter(output_file, self)
317308
self._execute(operation, reader, writer)
318309

319310
else:
320311

321312
file_name = path.basename(args[0])
322313
message = (
323-
'Command {0} appears to be statically configured and static '
324-
'configuration is unsupported by splunklib.searchcommands. '
325-
'Please ensure that default/commands.conf contains this '
326-
'stanza: '
327-
'[{0}] | '
328-
'filename = {1} | '
329-
'supports_getinfo = true | '
330-
'supports_rawargs = true | '
331-
'outputheader = true'.format(type(self).name, file_name))
314+
u'Command {0} appears to be statically configured and static '
315+
u'configuration is unsupported by splunklib.searchcommands. '
316+
u'Please ensure that default/commands.conf contains this '
317+
u'stanza:\n'
318+
u'[{0}]\n'
319+
u'filename = {1}\n'
320+
u'supports_getinfo = true\n'
321+
u'supports_rawargs = true\n'
322+
u'outputheader = true'.format(type(self).name, file_name))
332323
raise NotImplementedError(message)
333324

334-
except Exception as error:
325+
except SystemExit:
326+
raise
327+
328+
except:
329+
330+
import traceback
331+
import sys
332+
333+
error_type, error_message, error_traceback = sys.exc_info()
334+
self.logger.error(traceback.format_exc(error_traceback))
335335

336-
from traceback import format_exc
336+
origin = error_traceback
337+
338+
while origin.tb_next is not None:
339+
origin = origin.tb_next
340+
341+
filename = origin.tb_frame.f_code.co_filename
342+
lineno = origin.tb_lineno
343+
344+
self.write_error('%s at "%s", line %d : %s', error_type.__name__, filename, lineno, error_message)
337345

338-
writer = csv.DictWriter(output_file, self, fieldnames=['ERROR'])
339-
writer.writerow({'ERROR': error})
340-
self.logger.error(format_exc())
341346
exit(1)
342347

343348
return
@@ -348,11 +353,35 @@ def records(reader):
348353
yield record
349354
return
350355

351-
def _prepare(self, argv, input_file):
352-
raise NotImplementedError('SearchCommand._configure(self, argv)')
356+
# TODO: Is it possible to support anything other than write_error? It does not seem so.
357+
358+
def write_debug(self, message, *args):
359+
self._write_message(u'DEBUG', message, *args)
360+
return
361+
362+
def write_error(self, message, *args):
363+
self._write_message(u'ERROR', message, *args)
364+
return
365+
366+
def write_info(self, message, *args):
367+
self._write_message(u'INFO', message, *args)
368+
return
369+
370+
def write_warning(self, message, *args):
371+
self._write_message(u'WARN', message, *args)
353372

354373
def _execute(self, operation, reader, writer):
355-
raise NotImplementedError('SearchCommand._configure(self, argv)')
374+
raise NotImplementedError(u'SearchCommand._configure(self, argv)')
375+
376+
def _prepare(self, argv, input_file):
377+
raise NotImplementedError(u'SearchCommand._configure(self, argv)')
378+
379+
def _write_message(self, message_type, message_text, *args):
380+
import csv
381+
if len(args) > 0:
382+
message_text = message_text % args
383+
writer = csv.writer(self._output_file)
384+
writer.writerows([[], [message_type], [message_text]])
356385

357386
#endregion
358387

@@ -465,7 +494,7 @@ def needs_empty_results(self):
465494
@property
466495
def outputheader(self):
467496
""" Signals that the output of this command is a messages header
468-
followed by a blank line and csv search results.
497+
followed by a blank line and splunk_csv search results.
469498
470499
Fixed: :const:`True`
471500

splunklib/searchcommands/search_command_internals.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ def parse(self, argv, command):
295295
# Parse field names
296296

297297
command.fieldnames = command_args.group('fieldnames').split()
298-
command.logger.debug('%s: %s' % (type(command).__name__, command))
298+
command.logger.debug('%s: %s', type(command).__name__, command)
299299
return
300300

301301
@classmethod

splunklib/searchcommands/csv/__init__.py renamed to splunklib/searchcommands/splunk_csv/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@
2222

2323
# Sets the maximum allowable CSV field size.
2424
#
25-
# The default of the csv module is 128KB; upping to 10MB. See SPL-12117 for
25+
# The default of the splunk_csv module is 128KB; upping to 10MB. See SPL-12117 for
2626
# the background on issues surrounding field sizes.
2727
csv.field_size_limit(10485760)
File renamed without changes.
File renamed without changes.

splunklib/searchcommands/csv/dict_writer.py renamed to splunklib/searchcommands/splunk_csv/dict_writer.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,15 +88,18 @@ def _writerow(self, record):
8888
row = {}
8989

9090
for fieldname in self.fieldnames:
91-
value = record[fieldname]
92-
if isinstance(value, list):
93-
value, multi_value = self._encode_list(value)
94-
row[fieldname] = value
95-
row['__mv_' + fieldname] = multi_value
96-
elif isinstance(value, bool):
97-
row[fieldname] = int(value)
98-
else:
99-
row[fieldname] = value
91+
try:
92+
value = record[fieldname]
93+
if isinstance(value, list):
94+
value, multi_value = self._encode_list(value)
95+
row[fieldname] = value
96+
row['__mv_' + fieldname] = multi_value
97+
elif isinstance(value, bool):
98+
row[fieldname] = int(value)
99+
else:
100+
row[fieldname] = value
101+
except KeyError:
102+
row[fieldname] = ''
100103

101104
save_fieldnames = self.fieldnames
102105
self.fieldnames = self._fieldnames

0 commit comments

Comments
 (0)