Skip to content

Commit 337418e

Browse files
committed
Updated Splunk python SDK to 1.6.18
Corrected an issue where a DELETE combined with other operations could cause a stacktrace if using the `run_ko_diff` option
1 parent 406c288 commit 337418e

File tree

10 files changed

+131
-32
lines changed

10 files changed

+131
-32
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,11 @@ To do this you will need to install Version Control For SplunkCloud on your Splu
289289
[SplunkVersionControlCloud github](https://github.com/gjanders/SplunkVersionControlCloud)
290290

291291
## Release Notes
292+
### 1.2.7
293+
Updated Splunk python SDK to 1.6.18
294+
295+
Corrected an issue where a DELETE combined with other operations could cause a stacktrace if using the `run_ko_diff` option
296+
292297
### 1.2.6
293298
Updates to:
294299
`splunkversioncontrol_backup_class.py`

bin/splunkversioncontrol_backup_class.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -667,7 +667,7 @@ def process_ko_query_contents(self, results, tag):
667667
if self.file_per_ko:
668668
if not 'ko_name' in a_result:
669669
logger.info("i=\"%s\" ko_name is null, cannot identify an exact changed object \"%s\" tag=%s" % (self.stanzaName, a_result, tag))
670-
overall_matches_list = []
670+
overall_matches_list = []
671671
if 'user' in a_result:
672672
#TODO may not work on Windows
673673
find_str = "/" + a_result['user'] + "/"
@@ -676,7 +676,7 @@ def process_ko_query_contents(self, results, tag):
676676
find_str = a_result['ko_type']
677677
overall_matches_list = [ entry[0:entry.find(find_str)+len(find_str)] for entry in overall_matches if entry.find(find_str) != -1 ]
678678
if len(overall_matches_list) > 0:
679-
overall_matches = overall_matches + list(set(overall_matches_list))
679+
overall_matches = overall_matches + list(set(overall_matches_list))
680680
else:
681681
file_name = self.create_file_name(a_result['ko_name'])
682682
logger.debug("i=\"%s\" looking for file_name=%s tag=%s" % (self.stanzaName, file_name, tag))
@@ -690,7 +690,7 @@ def process_ko_query_contents(self, results, tag):
690690
if len(match) > 0:
691691
overall_matches = match
692692
else:
693-
if 'action' in a_result and a_result['action'].find("DELETE") == -1:
693+
if 'action' in a_result and "DELETE" in a_result['action']:
694694
logger.warn("i=\"%s\" looking for file_name=%s in file_str=%s tag=%s but did not find a match" % (self.stanzaName, file_name, file_str, tag))
695695
else:
696696
deleted_file_str = a_result['app_name'] + "/" + a_scope + "/" + a_result['ko_type'] + "/" + a_result['user'] + "/" + file_name

default/app.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ label = SplunkVersionControl
1212
[launcher]
1313
author = Gareth Anderson
1414
description = Version Control software for Splunk instances (backup/restore from git)
15-
version = 1.2.6
15+
version = 1.2.7
1616

1717
[package]
1818
id = SplunkVersionControl

lib/splunklib/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@
1616

1717
from __future__ import absolute_import
1818
from splunklib.six.moves import map
19-
__version_info__ = (1, 6, 16)
19+
__version_info__ = (1, 6, 18)
2020
__version__ = ".".join(map(str, __version_info__))

lib/splunklib/binding.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
import logging
3131
import socket
3232
import ssl
33-
import sys
3433
from base64 import b64encode
3534
from contextlib import contextmanager
3635
from datetime import datetime
@@ -39,7 +38,6 @@
3938
from xml.etree.ElementTree import XML
4039

4140
from splunklib import six
42-
from splunklib.six import StringIO
4341
from splunklib.six.moves import urllib
4442

4543
from .data import record
@@ -471,7 +469,7 @@ class Context(object):
471469
"""
472470
def __init__(self, handler=None, **kwargs):
473471
self.http = HttpLib(handler, kwargs.get("verify", False), key_file=kwargs.get("key_file"),
474-
cert_file=kwargs.get("cert_file")) # Default to False for backward compat
472+
cert_file=kwargs.get("cert_file"), context=kwargs.get("context")) # Default to False for backward compat
475473
self.token = kwargs.get("token", _NoAuthenticationToken)
476474
if self.token is None: # In case someone explicitly passes token=None
477475
self.token = _NoAuthenticationToken
@@ -1070,7 +1068,7 @@ def __init__(self, message, cause):
10701068
#
10711069

10721070
# Encode the given kwargs as a query string. This wrapper will also _encode
1073-
# a list value as a sequence of assignemnts to the corresponding arg name,
1071+
# a list value as a sequence of assignments to the corresponding arg name,
10741072
# for example an argument such as 'foo=[1,2,3]' will be encoded as
10751073
# 'foo=1&foo=2&foo=3'.
10761074
def _encode(**kwargs):
@@ -1137,9 +1135,9 @@ class HttpLib(object):
11371135
11381136
If using the default handler, SSL verification can be disabled by passing verify=False.
11391137
"""
1140-
def __init__(self, custom_handler=None, verify=False, key_file=None, cert_file=None):
1138+
def __init__(self, custom_handler=None, verify=False, key_file=None, cert_file=None, context=None):
11411139
if custom_handler is None:
1142-
self.handler = handler(verify=verify, key_file=key_file, cert_file=cert_file)
1140+
self.handler = handler(verify=verify, key_file=key_file, cert_file=cert_file, context=context)
11431141
else:
11441142
self.handler = custom_handler
11451143
self._cookies = {}
@@ -1351,7 +1349,7 @@ def readinto(self, byte_array):
13511349
return bytes_read
13521350

13531351

1354-
def handler(key_file=None, cert_file=None, timeout=None, verify=False):
1352+
def handler(key_file=None, cert_file=None, timeout=None, verify=False, context=None):
13551353
"""This class returns an instance of the default HTTP request handler using
13561354
the values you provide.
13571355
@@ -1363,6 +1361,8 @@ def handler(key_file=None, cert_file=None, timeout=None, verify=False):
13631361
:type timeout: ``integer`` or "None"
13641362
:param `verify`: Set to False to disable SSL verification on https connections.
13651363
:type verify: ``Boolean``
1364+
:param `context`: The SSLContext that can is used with the HTTPSConnection when verify=True is enabled and context is specified
1365+
:type context: ``SSLContext`
13661366
"""
13671367

13681368
def connect(scheme, host, port):
@@ -1376,6 +1376,10 @@ def connect(scheme, host, port):
13761376

13771377
if not verify:
13781378
kwargs['context'] = ssl._create_unverified_context()
1379+
elif context:
1380+
# verify is True in elif branch and context is not None
1381+
kwargs['context'] = context
1382+
13791383
return six.moves.http_client.HTTPSConnection(host, port, **kwargs)
13801384
raise ValueError("unsupported scheme: %s" % scheme)
13811385

@@ -1385,7 +1389,7 @@ def request(url, message, **kwargs):
13851389
head = {
13861390
"Content-Length": str(len(body)),
13871391
"Host": host,
1388-
"User-Agent": "splunk-sdk-python/1.6.16",
1392+
"User-Agent": "splunk-sdk-python/1.6.18",
13891393
"Accept": "*/*",
13901394
"Connection": "Close",
13911395
} # defaults

lib/splunklib/client.py

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ def connect(**kwargs):
295295
:type port: ``integer``
296296
:param scheme: The scheme for accessing the service (the default is "https").
297297
:type scheme: "https" or "http"
298-
:param verify: Enable (True) or disable (False) SSL verrification for
298+
:param verify: Enable (True) or disable (False) SSL verification for
299299
https connections. (optional, the default is True)
300300
:type verify: ``Boolean``
301301
:param `owner`: The owner context of the namespace (optional).
@@ -318,6 +318,8 @@ def connect(**kwargs):
318318
:type username: ``string``
319319
:param `password`: The password for the Splunk account.
320320
:type password: ``string``
321+
:param `context`: The SSLContext that can be used when setting verify=True (optional)
322+
:type context: ``SSLContext``
321323
:return: An initialized :class:`Service` connection.
322324
323325
**Example**::
@@ -365,7 +367,7 @@ class Service(_BaseService):
365367
:type port: ``integer``
366368
:param scheme: The scheme for accessing the service (the default is "https").
367369
:type scheme: "https" or "http"
368-
:param verify: Enable (True) or disable (False) SSL verrification for
370+
:param verify: Enable (True) or disable (False) SSL verification for
369371
https connections. (optional, the default is True)
370372
:type verify: ``Boolean``
371373
:param `owner`: The owner context of the namespace (optional; use "-" for wildcard).
@@ -401,6 +403,7 @@ class Service(_BaseService):
401403
def __init__(self, **kwargs):
402404
super(Service, self).__init__(**kwargs)
403405
self._splunk_version = None
406+
self._kvstore_owner = None
404407

405408
@property
406409
def apps(self):
@@ -673,12 +676,34 @@ def splunk_version(self):
673676
self._splunk_version = tuple([int(p) for p in self.info['version'].split('.')])
674677
return self._splunk_version
675678

679+
@property
680+
def kvstore_owner(self):
681+
"""Returns the KVStore owner for this instance of Splunk.
682+
683+
By default is the kvstore owner is not set, it will return "nobody"
684+
:return: A string with the KVStore owner.
685+
"""
686+
if self._kvstore_owner is None:
687+
self._kvstore_owner = "nobody"
688+
return self._kvstore_owner
689+
690+
@kvstore_owner.setter
691+
def kvstore_owner(self, value):
692+
"""
693+
kvstore is refreshed, when the owner value is changed
694+
"""
695+
self._kvstore_owner = value
696+
self.kvstore
697+
676698
@property
677699
def kvstore(self):
678700
"""Returns the collection of KV Store collections.
679701
702+
sets the owner for the namespace, before retrieving the KVStore Collection
703+
680704
:return: A :class:`KVStoreCollections` collection of :class:`KVStoreCollection` entities.
681705
"""
706+
self.namespace['owner'] = self.kvstore_owner
682707
return KVStoreCollections(self)
683708

684709
@property
@@ -856,7 +881,7 @@ class Entity(Endpoint):
856881
ent.whitelist
857882
858883
However, because some of the field names are not valid Python identifiers,
859-
the dictionary-like syntax is preferrable.
884+
the dictionary-like syntax is preferable.
860885
861886
The state of an :class:`Entity` object is cached, so accessing a field
862887
does not contact the server. If you think the values on the
@@ -3619,7 +3644,7 @@ def __init__(self, collection):
36193644
self.service = collection.service
36203645
self.collection = collection
36213646
self.owner, self.app, self.sharing = collection._proper_namespace()
3622-
self.path = 'storage/collections/data/' + UrlEncoded(self.collection.name) + '/'
3647+
self.path = 'storage/collections/data/' + UrlEncoded(self.collection.name, encode_slash=True) + '/'
36233648

36243649
def _get(self, url, **kwargs):
36253650
return self.service.get(self.path + url, owner=self.owner, app=self.app, sharing=self.sharing, **kwargs)
@@ -3640,6 +3665,11 @@ def query(self, **query):
36403665
:return: Array of documents retrieved by query.
36413666
:rtype: ``array``
36423667
"""
3668+
3669+
for key, value in query.items():
3670+
if isinstance(query[key], dict):
3671+
query[key] = json.dumps(value)
3672+
36433673
return json.loads(self._get('', **query).body.read().decode('utf-8'))
36443674

36453675
def query_by_id(self, id):
@@ -3652,7 +3682,7 @@ def query_by_id(self, id):
36523682
:return: Document with id
36533683
:rtype: ``dict``
36543684
"""
3655-
return json.loads(self._get(UrlEncoded(str(id))).body.read().decode('utf-8'))
3685+
return json.loads(self._get(UrlEncoded(str(id), encode_slash=True)).body.read().decode('utf-8'))
36563686

36573687
def insert(self, data):
36583688
"""
@@ -3664,6 +3694,8 @@ def insert(self, data):
36643694
:return: _id of inserted object
36653695
:rtype: ``dict``
36663696
"""
3697+
if isinstance(data, dict):
3698+
data = json.dumps(data)
36673699
return json.loads(self._post('', headers=KVStoreCollectionData.JSON_HEADER, body=data).body.read().decode('utf-8'))
36683700

36693701
def delete(self, query=None):
@@ -3686,7 +3718,7 @@ def delete_by_id(self, id):
36863718
36873719
:return: Result of DELETE request
36883720
"""
3689-
return self._delete(UrlEncoded(str(id)))
3721+
return self._delete(UrlEncoded(str(id), encode_slash=True))
36903722

36913723
def update(self, id, data):
36923724
"""
@@ -3700,7 +3732,9 @@ def update(self, id, data):
37003732
:return: id of replaced document
37013733
:rtype: ``dict``
37023734
"""
3703-
return json.loads(self._post(UrlEncoded(str(id)), headers=KVStoreCollectionData.JSON_HEADER, body=data).body.read().decode('utf-8'))
3735+
if isinstance(data, dict):
3736+
data = json.dumps(data)
3737+
return json.loads(self._post(UrlEncoded(str(id), encode_slash=True), headers=KVStoreCollectionData.JSON_HEADER, body=data).body.read().decode('utf-8'))
37043738

37053739
def batch_find(self, *dbqueries):
37063740
"""

lib/splunklib/modularinput/event_writer.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from __future__ import absolute_import
1616
import sys
1717

18-
from io import TextIOWrapper, TextIOBase
1918
from splunklib.six import ensure_str
2019
from .event import ET
2120

lib/splunklib/searchcommands/generating_command.py

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
# under the License.
1616

1717
from __future__ import absolute_import, division, print_function, unicode_literals
18+
import sys
1819

1920
from .decorators import ConfigurationSetting
2021
from .search_command import SearchCommand
@@ -212,13 +213,49 @@ def _execute(self, ifile, process):
212213

213214
def _execute_chunk_v2(self, process, chunk):
214215
count = 0
216+
records = []
215217
for row in process:
216-
self._record_writer.write_record(row)
218+
records.append(row)
217219
count += 1
218220
if count == self._record_writer._maxresultrows:
219-
self._finished = False
220-
return
221-
self._finished = True
221+
break
222+
223+
for row in records:
224+
self._record_writer.write_record(row)
225+
226+
if count == self._record_writer._maxresultrows:
227+
self._finished = False
228+
else:
229+
self._finished = True
230+
231+
def process(self, argv=sys.argv, ifile=sys.stdin, ofile=sys.stdout, allow_empty_input=True):
232+
""" Process data.
233+
234+
:param argv: Command line arguments.
235+
:type argv: list or tuple
236+
237+
:param ifile: Input data file.
238+
:type ifile: file
239+
240+
:param ofile: Output data file.
241+
:type ofile: file
242+
243+
:param allow_empty_input: For generating commands, it must be true. Doing otherwise will cause an error.
244+
:type allow_empty_input: bool
245+
246+
:return: :const:`None`
247+
:rtype: NoneType
248+
249+
"""
250+
251+
# Generating commands are expected to run on an empty set of inputs as the first command being run in a search,
252+
# also this class implements its own separate _execute_chunk_v2 method which does not respect allow_empty_input
253+
# so ensure that allow_empty_input is always True
254+
255+
if not allow_empty_input:
256+
raise ValueError("allow_empty_input cannot be False for Generating Commands")
257+
else:
258+
return super(GeneratingCommand, self).process(argv=argv, ifile=ifile, ofile=ofile, allow_empty_input=True)
222259

223260
# endregion
224261

lib/splunklib/searchcommands/internals.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,7 @@ def __init__(self, ofile, maxresultrows=None):
508508
self._chunk_count = 0
509509
self._pending_record_count = 0
510510
self._committed_record_count = 0
511+
self.custom_fields = set()
511512

512513
@property
513514
def is_flushed(self):
@@ -572,6 +573,7 @@ def write_record(self, record):
572573

573574
def write_records(self, records):
574575
self._ensure_validity()
576+
records = list(records)
575577
write_record = self._write_record
576578
for record in records:
577579
write_record(record)
@@ -593,6 +595,7 @@ def _write_record(self, record):
593595

594596
if fieldnames is None:
595597
self._fieldnames = fieldnames = list(record.keys())
598+
self._fieldnames.extend([i for i in self.custom_fields if i not in self._fieldnames])
596599
value_list = imap(lambda fn: (str(fn), str('__mv_') + str(fn)), fieldnames)
597600
self._writerow(list(chain.from_iterable(value_list)))
598601

0 commit comments

Comments
 (0)