Skip to content

Commit 5e7b7e0

Browse files
committed
Updated Splunk python SDK to 1.6.20
1 parent 24f4e6a commit 5e7b7e0

File tree

7 files changed

+69
-46
lines changed

7 files changed

+69
-46
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,9 @@ Shannon Davis (Splunk)
207207
Steven (malvidin on github)
208208

209209
# Release Notes
210+
## 2.3.9
211+
Updated Splunk python SDK to 1.6.20
212+
210213
## 2.3.8
211214
Merged pull request from Steven (malvidin on github)
212215

app.manifest

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"id": {
66
"group": null,
77
"name": "decrypt2",
8-
"version": "2.3.8"
8+
"version": "2.3.9"
99
},
1010
"author": [
1111
{

bin/lib/splunklib/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,5 @@ def setup_logging(level, log_format=DEFAULT_LOG_FORMAT, date_format=DEFAULT_DATE
3131
format=log_format,
3232
datefmt=date_format)
3333

34-
__version_info__ = (1, 6, 19)
34+
__version_info__ = (1, 6, 20)
3535
__version__ = ".".join(map(str, __version_info__))

bin/lib/splunklib/binding.py

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
import logging
3131
import socket
3232
import ssl
33+
import sys
34+
import time
3335
from base64 import b64encode
3436
from contextlib import contextmanager
3537
from datetime import datetime
@@ -295,8 +297,7 @@ def wrapper(self, *args, **kwargs):
295297
with _handle_auth_error("Autologin failed."):
296298
self.login()
297299
with _handle_auth_error(
298-
"Autologin succeeded, but there was an auth error on "
299-
"next request. Something is very wrong."):
300+
"Authentication Failed! If session token is used, it seems to have been expired."):
300301
return request_fun(self, *args, **kwargs)
301302
elif he.status == 401 and not self.autologin:
302303
raise AuthenticationError(
@@ -453,6 +454,12 @@ class Context(object):
453454
:type splunkToken: ``string``
454455
:param headers: List of extra HTTP headers to send (optional).
455456
:type headers: ``list`` of 2-tuples.
457+
:param retires: Number of retries for each HTTP connection (optional, the default is 0).
458+
NOTE THAT THIS MAY INCREASE THE NUMBER OF ROUND TRIP CONNECTIONS TO THE SPLUNK SERVER AND BLOCK THE
459+
CURRENT THREAD WHILE RETRYING.
460+
:type retries: ``int``
461+
:param retryDelay: How long to wait between connection attempts if `retries` > 0 (optional, defaults to 10s).
462+
:type retryDelay: ``int`` (in seconds)
456463
:param handler: The HTTP request handler (optional).
457464
:returns: A ``Context`` instance.
458465
@@ -470,7 +477,8 @@ class Context(object):
470477
"""
471478
def __init__(self, handler=None, **kwargs):
472479
self.http = HttpLib(handler, kwargs.get("verify", False), key_file=kwargs.get("key_file"),
473-
cert_file=kwargs.get("cert_file"), context=kwargs.get("context")) # Default to False for backward compat
480+
cert_file=kwargs.get("cert_file"), context=kwargs.get("context"), # Default to False for backward compat
481+
retries=kwargs.get("retries", 0), retryDelay=kwargs.get("retryDelay", 10))
474482
self.token = kwargs.get("token", _NoAuthenticationToken)
475483
if self.token is None: # In case someone explicitly passes token=None
476484
self.token = _NoAuthenticationToken
@@ -499,13 +507,13 @@ def get_cookies(self):
499507
return self.http._cookies
500508

501509
def has_cookies(self):
502-
"""Returns true if the ``HttpLib`` member of this instance has at least
503-
one cookie stored.
510+
"""Returns true if the ``HttpLib`` member of this instance has auth token stored.
504511
505-
:return: ``True`` if there is at least one cookie, else ``False``
512+
:return: ``True`` if there is auth token present, else ``False``
506513
:rtype: ``bool``
507514
"""
508-
return len(self.get_cookies()) > 0
515+
auth_token_key = "splunkd_"
516+
return any(auth_token_key in key for key in self.get_cookies().keys())
509517

510518
# Shared per-context request headers
511519
@property
@@ -800,9 +808,6 @@ def request(self, path_segment, method="GET", headers=None, body={},
800808
:type app: ``string``
801809
:param sharing: The sharing mode of the namespace (optional).
802810
:type sharing: ``string``
803-
:param query: All other keyword arguments, which are used as query
804-
parameters.
805-
:type query: ``string``
806811
:return: The response from the server.
807812
:rtype: ``dict`` with keys ``body``, ``headers``, ``reason``,
808813
and ``status``
@@ -1157,12 +1162,14 @@ class HttpLib(object):
11571162
11581163
If using the default handler, SSL verification can be disabled by passing verify=False.
11591164
"""
1160-
def __init__(self, custom_handler=None, verify=False, key_file=None, cert_file=None, context=None):
1165+
def __init__(self, custom_handler=None, verify=False, key_file=None, cert_file=None, context=None, retries=0, retryDelay=10):
11611166
if custom_handler is None:
11621167
self.handler = handler(verify=verify, key_file=key_file, cert_file=cert_file, context=context)
11631168
else:
11641169
self.handler = custom_handler
11651170
self._cookies = {}
1171+
self.retries = retries
1172+
self.retryDelay = retryDelay
11661173

11671174
def delete(self, url, headers=None, **kwargs):
11681175
"""Sends a DELETE request to a URL.
@@ -1276,7 +1283,16 @@ def request(self, url, message, **kwargs):
12761283
its structure).
12771284
:rtype: ``dict``
12781285
"""
1279-
response = self.handler(url, message, **kwargs)
1286+
while True:
1287+
try:
1288+
response = self.handler(url, message, **kwargs)
1289+
break
1290+
except Exception:
1291+
if self.retries <= 0:
1292+
raise
1293+
else:
1294+
time.sleep(self.retryDelay)
1295+
self.retries -= 1
12801296
response = record(response)
12811297
if 400 <= response.status:
12821298
raise HTTPError(response)
@@ -1414,7 +1430,7 @@ def request(url, message, **kwargs):
14141430
head = {
14151431
"Content-Length": str(len(body)),
14161432
"Host": host,
1417-
"User-Agent": "splunk-sdk-python/1.6.19",
1433+
"User-Agent": "splunk-sdk-python/1.6.20",
14181434
"Accept": "*/*",
14191435
"Connection": "Close",
14201436
} # defaults

bin/lib/splunklib/client.py

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,10 @@ def _load_atom_entries(response):
226226

227227

228228
# Load the sid from the body of the given response
229-
def _load_sid(response):
229+
def _load_sid(response, output_mode):
230+
if output_mode == "json":
231+
json_obj = json.loads(response.body.read())
232+
return json_obj.get('sid')
230233
return _load_atom(response).response.sid
231234

232235

@@ -320,6 +323,11 @@ def connect(**kwargs):
320323
:type username: ``string``
321324
:param `password`: The password for the Splunk account.
322325
:type password: ``string``
326+
:param retires: Number of retries for each HTTP connection (optional, the default is 0).
327+
NOTE THAT THIS MAY INCREASE THE NUMBER OF ROUND TRIP CONNECTIONS TO THE SPLUNK SERVER.
328+
:type retries: ``int``
329+
:param retryDelay: How long to wait between connection attempts if `retries` > 0 (optional, defaults to 10s).
330+
:type retryDelay: ``int`` (in seconds)
323331
:param `context`: The SSLContext that can be used when setting verify=True (optional)
324332
:type context: ``SSLContext``
325333
:return: An initialized :class:`Service` connection.
@@ -388,6 +396,11 @@ class Service(_BaseService):
388396
:param `password`: The password, which is used to authenticate the Splunk
389397
instance.
390398
:type password: ``string``
399+
:param retires: Number of retries for each HTTP connection (optional, the default is 0).
400+
NOTE THAT THIS MAY INCREASE THE NUMBER OF ROUND TRIP CONNECTIONS TO THE SPLUNK SERVER.
401+
:type retries: ``int``
402+
:param retryDelay: How long to wait between connection attempts if `retries` > 0 (optional, defaults to 10s).
403+
:type retryDelay: ``int`` (in seconds)
391404
:return: A :class:`Service` instance.
392405
393406
**Example**::
@@ -859,32 +872,21 @@ class Entity(Endpoint):
859872
860873
``Entity`` provides the majority of functionality required by entities.
861874
Subclasses only implement the special cases for individual entities.
862-
For example for deployment serverclasses, the subclass makes whitelists and
863-
blacklists into Python lists.
875+
For example for saved searches, the subclass makes fields like ``action.email``,
876+
``alert_type``, and ``search`` available.
864877
865878
An ``Entity`` is addressed like a dictionary, with a few extensions,
866-
so the following all work::
867-
868-
ent['email.action']
869-
ent['disabled']
870-
ent['whitelist']
871-
872-
Many endpoints have values that share a prefix, such as
873-
``email.to``, ``email.action``, and ``email.subject``. You can extract
874-
the whole fields, or use the key ``email`` to get a dictionary of
875-
all the subelements. That is, ``ent['email']`` returns a
876-
dictionary with the keys ``to``, ``action``, ``subject``, and so on. If
877-
there are multiple levels of dots, each level is made into a
878-
subdictionary, so ``email.body.salutation`` can be accessed at
879-
``ent['email']['body']['salutation']`` or
880-
``ent['email.body.salutation']``.
879+
so the following all work, for example in saved searches::
880+
881+
ent['action.email']
882+
ent['alert_type']
883+
ent['search']
881884
882885
You can also access the fields as though they were the fields of a Python
883886
object, as in::
884887
885-
ent.email.action
886-
ent.disabled
887-
ent.whitelist
888+
ent.alert_type
889+
ent.search
888890
889891
However, because some of the field names are not valid Python identifiers,
890892
the dictionary-like syntax is preferable.
@@ -1093,8 +1095,6 @@ def content(self):
10931095
def disable(self):
10941096
"""Disables the entity at this endpoint."""
10951097
self.post("disable")
1096-
if self.service.restart_required:
1097-
self.service.restart(120)
10981098
return self
10991099

11001100
def enable(self):
@@ -2968,7 +2968,7 @@ def create(self, query, **kwargs):
29682968
if kwargs.get("exec_mode", None) == "oneshot":
29692969
raise TypeError("Cannot specify exec_mode=oneshot; use the oneshot method instead.")
29702970
response = self.post(search=query, **kwargs)
2971-
sid = _load_sid(response)
2971+
sid = _load_sid(response, kwargs.get("output_mode", None))
29722972
return Job(self.service, sid)
29732973

29742974
def export(self, query, **params):
@@ -3029,7 +3029,7 @@ def itemmeta(self):
30293029
def oneshot(self, query, **params):
30303030
"""Run a oneshot search and returns a streaming handle to the results.
30313031
3032-
The ``InputStream`` object streams XML fragments from the server. To parse this stream into usable Python
3032+
The ``InputStream`` object streams fragments from the server. To parse this stream into usable Python
30333033
objects, pass the handle to :class:`splunklib.results.JSONResultsReader` along with the query param
30343034
"output_mode='json'" ::
30353035
@@ -3184,7 +3184,7 @@ def dispatch(self, **kwargs):
31843184
:return: The :class:`Job`.
31853185
"""
31863186
response = self.post("dispatch", **kwargs)
3187-
sid = _load_sid(response)
3187+
sid = _load_sid(response, kwargs.get("output_mode", None))
31883188
return Job(self.service, sid)
31893189

31903190
@property

bin/lib/splunklib/results.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
accessing search results while avoiding buffering the result set, which can be
2424
very large.
2525
26-
To use the reader, instantiate :class:`ResultsReader` on a search result stream
26+
To use the reader, instantiate :class:`JSONResultsReader` on a search result stream
2727
as follows:::
2828
2929
reader = ResultsReader(result_stream)
@@ -55,7 +55,8 @@
5555

5656
__all__ = [
5757
"ResultsReader",
58-
"Message"
58+
"Message",
59+
"JSONResultsReader"
5960
]
6061

6162

@@ -308,11 +309,14 @@ class JSONResultsReader(object):
308309
:class:`Message` object for Splunk messages. This class has one field,
309310
``is_preview``, which is ``True`` when the results are a preview from a
310311
running search, or ``False`` when the results are from a completed search.
312+
311313
This function has no network activity other than what is implicit in the
312314
stream it operates on.
313-
:param `stream`: The stream to read from (any object that supports
314-
``.read()``).
315+
316+
:param `stream`: The stream to read from (any object that supports``.read()``).
317+
315318
**Example**::
319+
316320
import results
317321
response = ... # the body of an HTTP response
318322
reader = results.JSONResultsReader(response)

default/app.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ is_visible = false
1212
[launcher]
1313
author = Gareth Anderson
1414
description = A library of common routines to analyze malware and data exfiltration communications (based on the work of Michael Zalewski).
15-
version = 2.3.8
15+
version = 2.3.9
1616

0 commit comments

Comments
 (0)