Skip to content

Commit 09e9eab

Browse files
author
David Noble
committed
DVPL-3313: splunklib | Improve the SDK to make it easier to talk back to Splunk's API from within a modular input or custom search command
Part 2: splunklib.modularinput Added Script.service property that returns a service object or None. Verified with automated unit tests, ad-hoc integration tests, and code review (thanks Shakeel). The service object is created from the Server URI and Session Key passed to the command invocation on the input stream. It is available as soon as the :code:`Script.stream_events` method is called. The script.service property returns a value of None, if you call it before the Script.stream_events` method is called. Signed-off-by: David Noble <[email protected]>
1 parent 2c4fd04 commit 09e9eab

File tree

2 files changed

+96
-13
lines changed

2 files changed

+96
-13
lines changed

splunklib/modularinput/script.py

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
# under the License.
1414

1515
from abc import ABCMeta, abstractmethod
16+
from urlparse import urlsplit
1617
import sys
1718

19+
from splunklib.client import Service
1820
from splunklib.modularinput.event_writer import EventWriter
1921
from splunklib.modularinput.input_definition import InputDefinition
2022
from splunklib.modularinput.validation_definition import ValidationDefinition
@@ -37,6 +39,10 @@ class Script(object):
3739
"""
3840
__metaclass__ = ABCMeta
3941

42+
def __init__(self):
43+
self._input_definition = None
44+
self._service = None
45+
4046
def run(self, args):
4147
"""Runs this modular input
4248
@@ -59,19 +65,22 @@ def run_script(self, args, event_writer, input_stream):
5965

6066
try:
6167
if len(args) == 1:
62-
# This script is running as an input. Input definitions will be passed on stdin
63-
# as XML, and the script will write events on stdout and log entries on stderr.
64-
input_definition = InputDefinition.parse(input_stream)
65-
self.stream_events(input_definition, event_writer)
68+
# This script is running as an input. Input definitions will be
69+
# passed on stdin as XML, and the script will write events on
70+
# stdout and log entries on stderr.
71+
self._input_definition = InputDefinition.parse(input_stream)
72+
self.stream_events(self._input_definition, event_writer)
6673
event_writer.close()
6774
return 0
6875

6976
elif str(args[1]).lower() == "--scheme":
70-
# Splunk has requested XML specifying the scheme for this modular input.
71-
# Return it and exit.
77+
# Splunk has requested XML specifying the scheme for this
78+
# modular input Return it and exit.
7279
scheme = self.get_scheme()
7380
if scheme is None:
74-
event_writer.log(EventWriter.FATAL, "Modular input script returned a null scheme.")
81+
event_writer.log(
82+
EventWriter.FATAL,
83+
"Modular input script returned a null scheme.")
7584
return 1
7685
else:
7786
event_writer.write_xml_document(scheme.to_xml())
@@ -97,6 +106,39 @@ def run_script(self, args, event_writer, input_stream):
97106
event_writer._err.write(err_string)
98107
return 1
99108

109+
@property
110+
def service(self):
111+
""" Returns a Splunk service object for this script invocation.
112+
113+
The service object is created from the Splunkd URI and session key
114+
passed to the command invocation on the modular input stream. It is
115+
available as soon as the :code:`Script.stream_events` method is
116+
called.
117+
118+
:return: :class:splunklib.client.Service. A value of None is returned,
119+
if you call this method before the :code:`Script.stream_events` method
120+
is called.
121+
122+
"""
123+
if self._service is not None:
124+
return self._service
125+
126+
if self._input_definition is None:
127+
return None
128+
129+
splunkd_uri = self._input_definition.metadata["server_uri"]
130+
session_key = self._input_definition.metadata["session_key"]
131+
132+
scheme, netloc, _, _, _ = urlsplit(splunkd_uri, allow_fragments=False)
133+
134+
splunkd_host, splunkd_port = netloc.split(':')
135+
136+
self._service = Service(
137+
scheme=scheme, host=splunkd_host, port=splunkd_port,
138+
token=session_key)
139+
140+
return self._service
141+
100142
@abstractmethod
101143
def get_scheme(self):
102144
"""The scheme defines the parameters understood by this modular input.
@@ -105,13 +147,16 @@ def get_scheme(self):
105147
"""
106148

107149
def validate_input(self, definition):
108-
"""Handles external validation for modular input kinds. When Splunk
109-
calls a modular input script in validation mode, it will pass in an XML document
110-
giving information about the Splunk instance (so you can call back into it if needed)
111-
and the name and parameters of the proposed input.
150+
"""Handles external validation for modular input kinds.
151+
152+
When Splunk calls a modular input script in validation mode, it will
153+
pass in an XML document giving information about the Splunk instance (so
154+
you can call back into it if needed) and the name and parameters of the
155+
proposed input.
112156
113-
If this function does not throw an exception, the validation is assumed to succeed.
114-
Otherwise any errors thrown will be turned into a string and logged back to Splunk.
157+
If this function does not throw an exception, the validation is assumed
158+
to succeed. Otherwise any errors thrown will be turned into a string and
159+
logged back to Splunk.
115160
116161
The default implementation always passes.
117162

tests/modularinput/test_script.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# under the License.
1414

1515
from tests.modularinput.modularinput_testlib import unittest, xml_compare, data_open
16+
from splunklib.client import Service
1617
from splunklib.modularinput.argument import Argument
1718
from splunklib.modularinput.event import Event
1819
from splunklib.modularinput.event_writer import EventWriter
@@ -210,5 +211,42 @@ def stream_events(self, inputs, ew):
210211

211212
self.assertTrue(xml_compare(expected, found))
212213

214+
def test_service_property(self):
215+
""" Check that Script.service returns a valid Service instance as soon
216+
as the stream_events method is called, but not before.
217+
218+
"""
219+
220+
# Override abstract methods
221+
class NewScript(Script):
222+
def __init__(self, test):
223+
super(NewScript, self).__init__()
224+
self.test = test
225+
226+
def get_scheme(self):
227+
return None
228+
229+
def stream_events(self, inputs, ew):
230+
service = self.service
231+
self.test.assertIsInstance(service, Service)
232+
self.test.assertEqual(str(service.authority), inputs.metadata['server_uri'])
233+
234+
script = NewScript(self)
235+
input_configuration = data_open("data/conf_with_2_inputs.xml")
236+
237+
out = StringIO()
238+
err = StringIO()
239+
ew = EventWriter(out, err)
240+
241+
self.assertEqual(script.service, None)
242+
243+
return_value = script.run_script(
244+
[TEST_SCRIPT_PATH], ew, input_configuration)
245+
246+
self.assertEqual(0, return_value)
247+
self.assertEqual("", err.getvalue())
248+
249+
return
250+
213251
if __name__ == "__main__":
214252
unittest.main()

0 commit comments

Comments
 (0)