From 0442accf6d81201268b037243d0b7b87a3adc75a Mon Sep 17 00:00:00 2001 From: yubol Date: Tue, 5 Jun 2018 14:19:56 +0800 Subject: [PATCH 01/37] Add return code for invalid args block Return non-zero rc instead of None --- splunklib/modularinput/script.py | 1 + 1 file changed, 1 insertion(+) diff --git a/splunklib/modularinput/script.py b/splunklib/modularinput/script.py index f9af0f887..040a07d9f 100755 --- a/splunklib/modularinput/script.py +++ b/splunklib/modularinput/script.py @@ -102,6 +102,7 @@ def run_script(self, args, event_writer, input_stream): err_string = "ERROR Invalid arguments to modular input script:" + ' '.join( args) event_writer._err.write(err_string) + return 1 except Exception as e: err_string = EventWriter.ERROR + str(e) From 0e0d872eb79d1a60747c3e7dbb8a64a3b6a1cf42 Mon Sep 17 00:00:00 2001 From: Austin Brogle Date: Wed, 6 Jun 2018 15:12:36 -0700 Subject: [PATCH 02/37] Added headers argument to get requests so that additional headers can be passed in similar to post reqeusts --- splunklib/binding.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/splunklib/binding.py b/splunklib/binding.py index 77a4532ae..9ba6d62dd 100644 --- a/splunklib/binding.py +++ b/splunklib/binding.py @@ -613,7 +613,7 @@ def delete(self, path_segment, owner=None, app=None, sharing=None, **query): @_authentication @_log_duration - def get(self, path_segment, owner=None, app=None, sharing=None, **query): + def get(self, path_segment, owner=None, app=None, headers=None, sharing=None, **query): """Performs a GET operation from the REST path segment with the given namespace and query. @@ -636,6 +636,8 @@ def get(self, path_segment, owner=None, app=None, sharing=None, **query): :type owner: ``string`` :param app: The app context of the namespace (optional). :type app: ``string`` + :param headers: List of extra HTTP headers to send (optional). + :type headers: ``list`` of 2-tuples. :param sharing: The sharing mode of the namespace (optional). :type sharing: ``string`` :param query: All other keyword arguments, which are used as query @@ -663,10 +665,14 @@ def get(self, path_segment, owner=None, app=None, sharing=None, **query): c.logout() c.get('apps/local') # raises AuthenticationError """ + if headers is None: + headers = [] + path = self.authority + self._abspath(path_segment, owner=owner, app=app, sharing=sharing) logging.debug("GET request to %s (body: %s)", path, repr(query)) - response = self.http.get(path, self._auth_headers, **query) + all_headers = headers + self._auth_headers + response = self.http.get(path, all_headers, **query) return response @_authentication @@ -1190,7 +1196,7 @@ def post(self, url, headers=None, **kwargs): # to support the receivers/stream endpoint. if 'body' in kwargs: # We only use application/x-www-form-urlencoded if there is no other - # Content-Type header present. This can happen in cases where we + # Content-Type header present. This can happen in cases where we # send requests as application/json, e.g. for KV Store. if len([x for x in headers if x[0].lower() == "content-type"]) == 0: headers.append(("Content-Type", "application/x-www-form-urlencoded")) From 8bcf9d929cd0053e9bab72d0fc1d7e3a5ae89286 Mon Sep 17 00:00:00 2001 From: Austin Brogle Date: Wed, 13 Jun 2018 11:08:58 -0700 Subject: [PATCH 03/37] Added the option of always adding a particular header to a request. Also fixed unicode xml decoding error --- splunklib/binding.py | 26 ++++++++++++++------------ splunklib/client.py | 44 +++++++++++++++++++++++++------------------- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/splunklib/binding.py b/splunklib/binding.py index 9ba6d62dd..01ebf91f8 100644 --- a/splunklib/binding.py +++ b/splunklib/binding.py @@ -25,30 +25,30 @@ """ from __future__ import absolute_import + +import io import logging import socket import ssl -from io import BytesIO - -from splunklib.six.moves import urllib -import io import sys - from base64 import b64encode +from contextlib import contextmanager from datetime import datetime from functools import wraps +from io import BytesIO +from xml.etree.ElementTree import XML + +from splunklib import six from splunklib.six import StringIO +from splunklib.six.moves import urllib -from contextlib import contextmanager +from .data import record -from xml.etree.ElementTree import XML -from splunklib import six try: from xml.etree.ElementTree import ParseError except ImportError as e: from xml.parsers.expat import ExpatError as ParseError -from .data import record __all__ = [ "AuthenticationError", @@ -478,6 +478,7 @@ def __init__(self, handler=None, **kwargs): self.password = kwargs.get("password", "") self.basic = kwargs.get("basic", False) self.autologin = kwargs.get("autologin", False) + self.additional_headers = kwargs.get("headers", []) # Store any cookies in the self.http._cookies dict if "cookie" in kwargs and kwargs['cookie'] not in [None, _NoAuthenticationToken]: @@ -671,7 +672,7 @@ def get(self, path_segment, owner=None, app=None, headers=None, sharing=None, ** path = self.authority + self._abspath(path_segment, owner=owner, app=app, sharing=sharing) logging.debug("GET request to %s (body: %s)", path, repr(query)) - all_headers = headers + self._auth_headers + all_headers = headers + self.additional_headers + self._auth_headers response = self.http.get(path, all_headers, **query) return response @@ -744,7 +745,7 @@ def post(self, path_segment, owner=None, app=None, sharing=None, headers=None, * path = self.authority + self._abspath(path_segment, owner=owner, app=app, sharing=sharing) logging.debug("POST request to %s (body: %s)", path, repr(query)) - all_headers = headers + self._auth_headers + all_headers = headers + self.additional_headers + self._auth_headers response = self.http.post(path, all_headers, **query) return response @@ -810,7 +811,7 @@ def request(self, path_segment, method="GET", headers=None, body="", path = self.authority \ + self._abspath(path_segment, owner=owner, app=app, sharing=sharing) - all_headers = headers + self._auth_headers + all_headers = headers + self.additional_headers + self._auth_headers logging.debug("%s request to %s (headers: %s, body: %s)", method, path, str(all_headers), repr(body)) response = self.http.request(path, @@ -864,6 +865,7 @@ def login(self): self.authority + self._abspath("/services/auth/login"), username=self.username, password=self.password, + headers=self.additional_headers, cookie="1") # In Splunk 6.2+, passing "cookie=1" will return the "set-cookie" header body = response.body.read() diff --git a/splunklib/client.py b/splunklib/client.py index 4664c0d01..1e624ba42 100644 --- a/splunklib/client.py +++ b/splunklib/client.py @@ -58,19 +58,22 @@ my_app.package() # Creates a compressed package of this application """ +import contextlib import datetime import json -from splunklib.six.moves import urllib import logging -from time import sleep -from datetime import datetime, timedelta import socket -import contextlib +from datetime import datetime, timedelta +from time import sleep from splunklib import six -from .binding import Context, HTTPError, AuthenticationError, namespace, UrlEncoded, _encode, _make_cookie_header, _NoAuthenticationToken -from .data import record +from splunklib.six.moves import urllib + from . import data +from .binding import (AuthenticationError, Context, HTTPError, UrlEncoded, + _encode, _make_cookie_header, _NoAuthenticationToken, + namespace) +from .data import record __all__ = [ "connect", @@ -193,8 +196,11 @@ def _path(base, name): # Load an atom record from the body of the given response +# this will ultimately be sent to an xml ElementTree so we +# should use the xmlcharrefreplace option def _load_atom(response, match=None): - return data.load(response.body.read().decode('utf-8'), match) + return data.load(response.body.read() + .decode('utf-8', 'xmlcharrefreplace'), match) # Load an array of atom entries from the body of the given response @@ -557,7 +563,7 @@ def restart(self, timeout=None): # This message will be deleted once the server actually restarts. self.messages.create(name="restart_required", **msg) result = self.post("/services/server/control/restart") - if timeout is None: + if timeout is None: return result start = datetime.now() diff = timedelta(seconds=timeout) @@ -1631,7 +1637,7 @@ def get(self, name="", owner=None, app=None, sharing=None, **query): and ``status`` Example: - + import splunklib.client s = client.service(...) saved_searches = s.saved_searches @@ -1685,7 +1691,7 @@ def __getitem__(self, key): # The superclass implementation is designed for collections that contain # entities. This collection (Configurations) contains collections # (ConfigurationFile). - # + # # The configurations endpoint returns multiple entities when we ask for a single file. # This screws up the default implementation of __getitem__ from Collection, which thinks # that multiple entities means a name collision, so we have to override it here. @@ -1749,9 +1755,9 @@ class Stanza(Entity): """This class contains a single configuration stanza.""" def submit(self, stanza): - """Adds keys to the current configuration stanza as a + """Adds keys to the current configuration stanza as a dictionary of key-value pairs. - + :param stanza: A dictionary of key-value pairs for the stanza. :type stanza: ``dict`` :return: The :class:`Stanza` object. @@ -1962,7 +1968,7 @@ def attach(self, host=None, source=None, sourcetype=None): cookie_or_auth_header.encode('utf-8'), b"X-Splunk-Input-Mode: Streaming\r\n", b"\r\n"] - + for h in headers: sock.write(h) return sock @@ -3695,13 +3701,13 @@ def batch_find(self, *dbqueries): :param dbqueries: Array of individual queries as dictionaries :type dbqueries: ``array`` of ``dict`` - + :return: Results of each query :rtype: ``array`` of ``array`` """ - if len(dbqueries) < 1: + if len(dbqueries) < 1: raise Exception('Must have at least one query.') - + data = json.dumps(dbqueries) return json.loads(self._post('batch_find', headers=KVStoreCollectionData.JSON_HEADER, body=data).body.read().decode('utf-8')) @@ -3712,13 +3718,13 @@ def batch_save(self, *documents): :param documents: Array of documents to save as dictionaries :type documents: ``array`` of ``dict`` - + :return: Results of update operation as overall stats :rtype: ``dict`` """ - if len(documents) < 1: + if len(documents) < 1: raise Exception('Must have at least one document.') - + data = json.dumps(documents) return json.loads(self._post('batch_save', headers=KVStoreCollectionData.JSON_HEADER, body=data).body.read().decode('utf-8')) From 353d773cc4c55b5519a313c62193ca392e8a3a40 Mon Sep 17 00:00:00 2001 From: Austin Brogle Date: Wed, 13 Jun 2018 11:52:15 -0700 Subject: [PATCH 04/37] Adding additional comments to denote that a headers object is possible to pass in as a kwarg --- splunklib/binding.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/splunklib/binding.py b/splunklib/binding.py index 01ebf91f8..57ae04a5b 100644 --- a/splunklib/binding.py +++ b/splunklib/binding.py @@ -449,6 +449,8 @@ class Context(object): :type username: ``string`` :param password: The password for the Splunk account. :type password: ``string`` + :param headers: List of extra HTTP headers to send (optional). + :type headers: ``list`` of 2-tuples. :param handler: The HTTP request handler (optional). :returns: A ``Context`` instance. @@ -976,6 +978,8 @@ def connect(**kwargs): :type username: ``string`` :param password: The password for the Splunk account. :type password: ``string`` + :param headers: List of extra HTTP headers to send (optional). + :type headers: ``list`` of 2-tuples. :param autologin: When ``True``, automatically tries to log in again if the session terminates. :type autologin: ``Boolean`` From 9ba6b373896b306b152ae01540a684cfacab6ee5 Mon Sep 17 00:00:00 2001 From: Shakeel Mohamed Date: Wed, 27 Jun 2018 17:09:47 -0700 Subject: [PATCH 05/37] auto-doc all class constructor parameters --- docs/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/conf.py b/docs/conf.py index c68aa66b6..4755a4b89 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -248,3 +248,5 @@ # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' + +autoclass_content = 'both' From b9de11df55bbbc889bc4cccaeaddef9ab41d1c51 Mon Sep 17 00:00:00 2001 From: Shakeel Mohamed Date: Thu, 27 Sep 2018 13:40:09 -0700 Subject: [PATCH 06/37] Searchcommands: add full support for unicode Python's cstringio doesn't support unicode characters, when encountering them Exceptions are raised. We've decided to use stringio instead, trading potential performance improvements for full unicode support. In Python 2, this may lead to a slight performance hit since we're no longer explicitly using the native version. However, in Python 3 stringio will automatically use the native version if available and there should be no noticeable performance changes. --- splunklib/searchcommands/internals.py | 2 +- splunklib/searchcommands/search_command.py | 3 +-- splunklib/searchcommands/validators.py | 2 +- tests/searchcommands/test_search_command.py | 5 +++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/splunklib/searchcommands/internals.py b/splunklib/searchcommands/internals.py index 78e643b0d..02634c081 100644 --- a/splunklib/searchcommands/internals.py +++ b/splunklib/searchcommands/internals.py @@ -22,7 +22,7 @@ from collections import OrderedDict # must be python 2.7 except ImportError: from ..ordereddict import OrderedDict -from splunklib.six.moves import cStringIO as StringIO +from splunklib.six.moves import StringIO from itertools import chain from splunklib.six.moves import map as imap from json import JSONDecoder, JSONEncoder diff --git a/splunklib/searchcommands/search_command.py b/splunklib/searchcommands/search_command.py index f3581026e..47918abb6 100644 --- a/splunklib/searchcommands/search_command.py +++ b/splunklib/searchcommands/search_command.py @@ -27,7 +27,7 @@ except ImportError: from ..ordereddict import OrderedDict from copy import deepcopy -from splunklib.six.moves import cStringIO as StringIO +from splunklib.six.moves import StringIO from itertools import chain, islice from splunklib.six.moves import filter as ifilter, map as imap, zip as izip from splunklib import six @@ -850,7 +850,6 @@ def _execute(self, ifile, process): @staticmethod def _read_chunk(ifile): - # noinspection PyBroadException try: header = ifile.readline() diff --git a/splunklib/searchcommands/validators.py b/splunklib/searchcommands/validators.py index eb1dc5cf0..66329375c 100644 --- a/splunklib/searchcommands/validators.py +++ b/splunklib/searchcommands/validators.py @@ -18,7 +18,7 @@ from json.encoder import encode_basestring_ascii as json_encode_string from collections import namedtuple -from splunklib.six.moves import cStringIO as StringIO +from splunklib.six.moves import StringIO from io import open import csv import os diff --git a/tests/searchcommands/test_search_command.py b/tests/searchcommands/test_search_command.py index 1e89e9ae1..827b5a886 100755 --- a/tests/searchcommands/test_search_command.py +++ b/tests/searchcommands/test_search_command.py @@ -1,4 +1,5 @@ #!/usr/bin/env python +# coding=utf-8 # # Copyright 2011-2015 Splunk, Inc. # @@ -22,7 +23,7 @@ from splunklib.searchcommands.search_command import SearchCommand from splunklib.client import Service -from splunklib.six.moves import cStringIO as StringIO +from splunklib.six.moves import StringIO from splunklib.six.moves import zip as izip from json.encoder import encode_basestring as encode_string from unittest import main, TestCase @@ -388,7 +389,7 @@ def test_process_scpv2(self): '"required_option_1=value_1",' '"required_option_2=value_2"' '],' - '"search": "%7C%20inputlookup%20tweets%20%7C%20countmatches%20fieldname%3Dword_count%20pattern%3D%22%5Cw%2B%22%20text%20record%3Dt%20%7C%20export%20add_timestamp%3Df%20add_offset%3Dt%20format%3Dcsv%20segmentation%3Draw",' + '"search": "A%7C%20inputlookup%20tweets%20%7C%20countmatches%20fieldname%3Dword_count%20pattern%3D%22%5Cw%2B%22%20text%20record%3Dt%20%7C%20export%20add_timestamp%3Df%20add_offset%3Dt%20format%3Dcsv%20segmentation%3Draw",' '"earliest_time": "0",' '"session_key": "0JbG1fJEvXrL6iYZw9y7tmvd6nHjTKj7ggaE7a4Jv5R0UIbeYJ65kThn^3hiNeoqzMT_LOtLpVR3Y8TIJyr5bkHUElMijYZ8l14wU0L4n^Oa5QxepsZNUIIQCBm^",' '"owner": "admin",' From 6894ef5338c3a9ebc71aca9d487bcd5bc665b8a4 Mon Sep 17 00:00:00 2001 From: Shakeel Mohamed Date: Thu, 27 Sep 2018 14:17:22 -0700 Subject: [PATCH 07/37] update assertion --- tests/searchcommands/test_search_command.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/searchcommands/test_search_command.py b/tests/searchcommands/test_search_command.py index 827b5a886..0f0d71c13 100755 --- a/tests/searchcommands/test_search_command.py +++ b/tests/searchcommands/test_search_command.py @@ -493,7 +493,7 @@ def test_process_scpv2(self): self.assertEqual(command_metadata.searchinfo.latest_time, 0.0) self.assertEqual(command_metadata.searchinfo.owner, 'admin') self.assertEqual(command_metadata.searchinfo.raw_args, command_metadata.searchinfo.args) - self.assertEqual(command_metadata.searchinfo.search, '| inputlookup tweets | countmatches fieldname=word_count pattern="\\w+" text record=t | export add_timestamp=f add_offset=t format=csv segmentation=raw') + self.assertEqual(command_metadata.searchinfo.search, 'A| inputlookup tweets | countmatches fieldname=word_count pattern="\\w+" text record=t | export add_timestamp=f add_offset=t format=csv segmentation=raw') self.assertEqual(command_metadata.searchinfo.session_key, '0JbG1fJEvXrL6iYZw9y7tmvd6nHjTKj7ggaE7a4Jv5R0UIbeYJ65kThn^3hiNeoqzMT_LOtLpVR3Y8TIJyr5bkHUElMijYZ8l14wU0L4n^Oa5QxepsZNUIIQCBm^') self.assertEqual(command_metadata.searchinfo.sid, '1433261372.158') self.assertEqual(command_metadata.searchinfo.splunk_version, '20150522') From 0eb516e78ddb67383eeba8c567aa0e222e115afe Mon Sep 17 00:00:00 2001 From: Shakeel Mohamed Date: Wed, 10 Oct 2018 09:18:58 -0700 Subject: [PATCH 08/37] Make the explorer example compatible w/ Python 3 ... by using the six library --- .gitignore | 3 ++- examples/explorer/explorer.py | 5 +++-- examples/explorer/server.py | 30 ++++++++++++++++-------------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index ebd4782f2..853591131 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,5 @@ dist/ examples/searchcommands_app/package/default/commands.conf examples/searchcommands_app/package/bin/packages tests/searchcommands/apps/app_with_logging_configuration/*.log -*.observed \ No newline at end of file +*.observed +venv/ \ No newline at end of file diff --git a/examples/explorer/explorer.py b/examples/explorer/explorer.py index 60a9aeff3..be3dc3279 100755 --- a/examples/explorer/explorer.py +++ b/examples/explorer/explorer.py @@ -27,7 +27,8 @@ except ImportError: raise Exception("Add the SDK repository to your PYTHONPATH to run the examples " "(e.g., export PYTHONPATH=~/splunk-sdk-python.") -import urllib + +from splunklib.six.moves import urllib PORT = 8080 @@ -57,7 +58,7 @@ def main(argv): args.append(('owner', opts.kwargs['owner'])) # Encode these arguments - args = urllib.urlencode(args) + args = urllib.parse.urlencode(args) # Launch the browser webbrowser.open("file://%s" % os.path.join(os.getcwd(), "explorer.html?%s" % args)) diff --git a/examples/explorer/server.py b/examples/explorer/server.py index 5f9caaf63..9626737c3 100755 --- a/examples/explorer/server.py +++ b/examples/explorer/server.py @@ -16,16 +16,18 @@ from __future__ import absolute_import from __future__ import print_function -import splunklib.six.moves.SimpleHTTPServer -import splunklib.six.moves.socketserver -import urllib2 import sys -import StringIO -from splunklib import six +import os + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..")) + +from splunklib.six import iteritems +from splunklib.six.moves import socketserver, SimpleHTTPServer, StringIO, urllib PORT = 8080 -class RedirectHandler(six.moves.SimpleHTTPServer.SimpleHTTPRequestHandler): + +class RedirectHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): def do_GET(self): redirect_url, headers = self.get_url_and_headers() if redirect_url is None: @@ -83,13 +85,13 @@ def make_request(self, url, method, data, headers): try: # Make the request - request = urllib2.Request(url, data, headers) + request = urllib.Request(url, data, headers) request.get_method = lambda: method - response = urllib2.urlopen(request) + response = urllib.urlopen(request) # We were successful, so send the response code self.send_response(response.code, message=response.msg) - for key, value in six.iteritems(dict(response.headers)): + for key, value in iteritems(dict(response.headers)): # Optionally log the headers #self.log_message("%s: %s" % (key, value)) @@ -105,16 +107,16 @@ def make_request(self, url, method, data, headers): # Copy the response to the output self.copyfile(response, self.wfile) - except urllib2.HTTPError as e: + except urllib.HTTPError as e: # On errors, log the response code and message self.log_message("Code: %s (%s)", e.code, e.msg) - for key, value in six.iteritems(dict(e.hdrs)): + for key, value in iteritems(dict(e.hdrs)): # On errors, we always log the headers self.log_message("%s: %s", key, value) response_text = e.fp.read() - response_file = StringIO.StringIO(response_text) + response_file = StringIO(response_text) # On errors, we also log the response text self.log_message("Response: %s", response_text) @@ -135,10 +137,10 @@ def make_request(self, url, method, data, headers): # Finally, send the error itself self.copyfile(response_file, self.wfile) -class ReuseableSocketTCPServer(six.moves.socketserver.TCPServer): +class ReuseableSocketTCPServer(socketserver.TCPServer): def __init__(self, *args, **kwargs): self.allow_reuse_address = True - six.moves.socketserver.TCPServer.__init__(self, *args, **kwargs) + socketserver.TCPServer.__init__(self, *args, **kwargs) def serve(port = PORT): Handler = RedirectHandler From 55802f4c628877f277bf560ea733ffef1985d242 Mon Sep 17 00:00:00 2001 From: kimox2 Date: Tue, 23 Oct 2018 17:04:11 +0100 Subject: [PATCH 09/37] Update binding.py --- splunklib/binding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/splunklib/binding.py b/splunklib/binding.py index 74d917377..c13611845 100644 --- a/splunklib/binding.py +++ b/splunklib/binding.py @@ -1280,8 +1280,8 @@ def peek(self, size): def close(self): """Closes this response.""" - if _connection: - _connection.close() + if self._connection: + self._connection.close() self._response.close() def read(self, size = None): From 93726384364f6799172a2326860b8cde980ce5dc Mon Sep 17 00:00:00 2001 From: Shakeel Mohamed Date: Tue, 30 Oct 2018 15:53:47 -0700 Subject: [PATCH 10/37] Run CI against Splunk 7.2 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 37640ff1e..d8d5f77b3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,8 +30,8 @@ before_install: - mkdir -p $SPLUNK_HOME/var/log/splunk env: - - SPLUNK_VERSION=6.6-sdk - SPLUNK_VERSION=7.0-sdk + - SPLUNK_VERSION=7.2-sdk language: python From 3ef3a66c00dbf0c58cedbfee305f861849551900 Mon Sep 17 00:00:00 2001 From: Shakeel Mohamed Date: Tue, 30 Oct 2018 17:22:00 -0700 Subject: [PATCH 11/37] tests: relax restart timeout --- tests/test_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_service.py b/tests/test_service.py index 1ae1f59af..56b88c061 100755 --- a/tests/test_service.py +++ b/tests/test_service.py @@ -113,7 +113,7 @@ def test_parse_fail(self): def test_restart(self): service = client.connect(**self.opts.kwargs) - self.service.restart(timeout=120) + self.service.restart(timeout=300) service.login() # Make sure we are awake def test_read_outputs_with_type(self): From 35ca7747b5eb327723a42053f57ea960c15892ce Mon Sep 17 00:00:00 2001 From: esp Date: Tue, 4 Dec 2018 15:13:03 -0800 Subject: [PATCH 12/37] Fix ssl verify to require certs when true (#233) - Correct logic to always look for cert files when verify=True - Pass key_file and cert_file to handler from HttpLib - Changed default value of verify to False for backward compat --- splunklib/binding.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/splunklib/binding.py b/splunklib/binding.py index b108d7616..5a29387fd 100644 --- a/splunklib/binding.py +++ b/splunklib/binding.py @@ -467,7 +467,8 @@ class Context(object): c = binding.Context(cookie="splunkd_8089=...") """ def __init__(self, handler=None, **kwargs): - self.http = HttpLib(handler, kwargs.get("verify", True)) + self.http = HttpLib(handler, kwargs.get("verify", False), key_file=kwargs.get("key_file"), + cert_file=kwargs.get("cert_file")) # Default to False for backward compat self.token = kwargs.get("token", _NoAuthenticationToken) if self.token is None: # In case someone explicitly passes token=None self.token = _NoAuthenticationToken @@ -1120,8 +1121,11 @@ class HttpLib(object): If using the default handler, SSL verification can be disabled by passing verify=False. """ - def __init__(self, custom_handler=None, verify=True): - self.handler = handler(verify=verify) if custom_handler is None else custom_handler + def __init__(self, custom_handler=None, verify=False, key_file=None, cert_file=None): + if custom_handler is None: + self.handler = handler(verify=verify, key_file=key_file, cert_file=cert_file) + else: + self.handler = custom_handler self._cookies = {} def delete(self, url, headers=None, **kwargs): @@ -1329,7 +1333,7 @@ def readinto(self, byte_array): return bytes_read -def handler(key_file=None, cert_file=None, timeout=None, verify=True): +def handler(key_file=None, cert_file=None, timeout=None, verify=False): """This class returns an instance of the default HTTP request handler using the values you provide. @@ -1353,7 +1357,7 @@ def connect(scheme, host, port): if cert_file is not None: kwargs['cert_file'] = cert_file # If running Python 2.7.9+, disable SSL certificate validation - if (sys.version_info >= (2,7,9) and key_file is None and cert_file is None) or not verify: + if (sys.version_info >= (2,7,9) and key_file is None and cert_file is None) and not verify: kwargs['context'] = ssl._create_unverified_context() return six.moves.http_client.HTTPSConnection(host, port, **kwargs) raise ValueError("unsupported scheme: %s" % scheme) From dae06ce691ac6b92f763ed16f1c91e37c06e638a Mon Sep 17 00:00:00 2001 From: Shakeel Mohamed Date: Tue, 22 Jan 2019 11:15:53 -0800 Subject: [PATCH 13/37] Remove scpv1 custom search command examples --- examples/custom_search/README.md | 137 ------------ examples/custom_search/bin/usercount.py | 196 ------------------ examples/custom_search/default/app.conf | 13 -- .../default/data/ui/nav/default.xml | 18 -- .../default/data/ui/views/README | 1 - examples/custom_search/local/app.conf | 3 - examples/custom_search/local/commands.conf | 7 - examples/custom_search/metadata/default.meta | 29 --- examples/custom_search/metadata/local.meta | 7 - tests/test_examples.py | 61 ------ 10 files changed, 472 deletions(-) delete mode 100644 examples/custom_search/README.md delete mode 100755 examples/custom_search/bin/usercount.py delete mode 100644 examples/custom_search/default/app.conf delete mode 100644 examples/custom_search/default/data/ui/nav/default.xml delete mode 100644 examples/custom_search/default/data/ui/views/README delete mode 100644 examples/custom_search/local/app.conf delete mode 100644 examples/custom_search/local/commands.conf delete mode 100644 examples/custom_search/metadata/default.meta delete mode 100644 examples/custom_search/metadata/local.meta diff --git a/examples/custom_search/README.md b/examples/custom_search/README.md deleted file mode 100644 index 07bf5911e..000000000 --- a/examples/custom_search/README.md +++ /dev/null @@ -1,137 +0,0 @@ -# Custom Search - -custom_search is a custom Splunk app (http://www.splunk.com/base/Documentation/latest/Developer/AppIntro) -that provides a single custom search command 'usercount'. - -The purpose of the app is to provide an example of how to define a custom search -command, how to configure it, and what the input and output should look like in -order to work with Splunk. Most of this is also documented in the Splunk -documentation at http://www.splunk.com/base/Documentation/latest/SearchReference/Aboutcustomsearchcommands - -## Example Commands - -* Count the number of processes each user has in a unix "top" event: - [examples/custom_search/bin/usercount.py](https://github.com/splunk/splunk-sdk-python/blob/master/examples/custom_search/bin/usercount.py) -* Count the top hashtags in a set of tweets: - [examples/twitted/twitted/bin/tophashtags.py](https://github.com/splunk/splunk-sdk-python/blob/master/examples/twitted/twitted/bin/tophashtags.py) -* Add a hashtags multivalue field to each tweet: - [examples/twitted/twitted/bin/hashtags.py](https://github.com/splunk/splunk-sdk-python/blob/master/examples/twitted/twitted/bin/hashtags.py) - -## Defining a Custom Search Command - -A custom search command is merely a Python script that reads input from stdin -and writes output to stdout. Input comes in as CSV (with an optional header), -and is in general meant to be read using Python's stdlib csv module -(using `csv.reader` or `csv.DictReader`). Output is also expected to be in CSV, -and is likewise meant to be used with `csv.writer` or `csv.DictWriter`. - -## Conceptual - -As noted above, a custom search command is just a Python script that reads data -in and writes data out. However, it might be useful to make a distinction -between two subtypes of custom search commands: - -* A streaming custom search command is one that is streamed data in. You can - think of it as applying a "function"/"transformation" to each event and then - writing out the result of that operation. It is a kind of "mapper". An - example of such a command might be a command that adds a field to each event. -* A non-streaming custom search command expects to have all the data before - it operates on it. As such, it is usually "reducing" the data into the - output by applying some sort of summary transformation on it. An example of - a non-streaming command is the 'stats' command, which will collect all the - data before it can calculate the statistics. - -Note that neither of these cases precludes having previews of the data, and you -can enable or disable preview functionality in the configuration. - -## Configuration - -Configuration of custom search commands is done in the local/commands.conf file -of your custom app. You can take a look at a few examples in the SDK: - -* [examples/custom_search/local/commands.conf](https://github.com/splunk/splunk-sdk-python/blob/master/examples/custom_search/local/commands.conf) -* [examples/twitted/twitted/local/commands.conf](https://github.com/splunk/splunk-sdk-python/blob/master/examples/twitted/twitted/local/commands.conf) - -The authoritative documentation for commands.conf can be found here: -http://www.splunk.com/base/Documentation/latest/Admin/commandsconf - -## Input - -The input format is just CSV, with an optional header. The general format -definition is: - -a. Several lines of header, in the form of "key:value" pairs, separated by - new lines. OPTIONAL -b. A blank newline -c. Data - -The presence of the header (and some fields in it) can be controlled in -commands.conf. - -Included an annotated sample input below. Python style '###' comments are used -to point out salient features. This input is truncated for brevity - you can see -the full input at tests/custom_search/usercount.in - -``` -### The following line is the first line of the header -authString:itayitay6e49d9164a4eced1a006f46d5710715c -sessionKey:6e49d9164a4eced1a006f46d5710715c -owner:itay -namespace:custom_search -keywords:%22%22%22sourcetype%3A%3Atop%22%22%20%22 -search:search%20sourcetype%3D%22top%22%20%7C%20head%202%20%7C%20usercount%20%7C%20head%20100%20%7C%20export -sid:1310074215.71 -realtime:0 -preview:0 -truncated:0 -### The above line is the last line of the header, following by the mandatory blank line. - -### Data starts in the line below. The first line includes the CSV "column headers", followed by the actual data for each row -"_cd","_indextime","_kv","_raw","_serial","_si","_sourcetype","_time",eventtype,host,index,linecount,punct,source,sourcetype,"splunk_server","tag::eventtype",timestamp -"28:138489",1310074203,1," PID USER PR NI VIRT RES SHR S pctCPU pctMEM cpuTIME COMMAND - 469 root ? ? 2378M 1568K 244K ? 7.2 ? 00:00.15 top - 95 _coreaudiod ? ? 2462M 12M 952K ? 5.3 ? 88:47.70 coreaudiod - 7722 itay ? ? 4506M 608M 99M ? 3.9 ? 75:02.81 pycharm -",0,"Octavian.local -os",top,1310074203,"top usb_device_registration_Linux_syslog","Octavian.local",os,120,"__________________________________________________",top,top,"Octavian.local","Linux -USB -device -os -process -registration -report -success -syslog -top",none -### This is the start of the second CSV record -"28:138067",1310074173,1," PID USER PR NI VIRT RES SHR S pctCPU pctMEM cpuTIME COMMAND - 369 root ? ? 2378M 1568K 244K ? 7.3 ? 00:00.15 top - 95 _coreaudiod ? ? 2462M 12M 952K ? 5.4 ? 88:46.09 coreaudiod - 7722 itay ? ? 4506M 608M 99M ? 3.9 ? 75:01.67 pycharm -",1,"Octavian.local -os",top,1310074173,"top usb_device_registration_Linux_syslog","Octavian.local",os,120,"__________________________________________________",top,top,"Octavian.local","Linux -USB -device -os -process -registration -report -success -syslog -top",none -### Above line is end of input -``` - -## Output - -Output of a custom search command is also in CSV. It follows the same format as -the input: an optional header, followed by a blank line, followed by the data. -Included below is a sample output for the above input: - -``` -### The configuration does not call for a header, so we start with the data immediately. The below line are the CSV column headers. -daemon,usbmuxd,windowserver,www,mdnsresponder,coreaudiod,itay,locationd,root,spotlight -1,1,1,1,1,1,73,1,37,1 -1,1,1,1,1,1,73,1,37,1 -### The end of the output. The preceding lines are the actual records for each row -``` \ No newline at end of file diff --git a/examples/custom_search/bin/usercount.py b/examples/custom_search/bin/usercount.py deleted file mode 100755 index 7346fd212..000000000 --- a/examples/custom_search/bin/usercount.py +++ /dev/null @@ -1,196 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2011-2015 Splunk, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"): you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from __future__ import absolute_import -import csv, sys -import os - -sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir, os.pardir))) - -from splunklib import six -from splunklib.six.moves import zip -from splunklib.six import StringIO -from splunklib.six.moves import urllib - -# Tees output to a logfile for debugging -class Logger: - def __init__(self, filename, buf = None): - self.log = open(filename, 'w') - self.buf = buf - - def flush(self): - self.log.flush() - - if self.buf is not None: - self.buf.flush() - - def write(self, message): - self.log.write(message) - self.log.flush() - - if self.buf is not None: - self.buf.write(message) - self.buf.flush() - -# Tees input as it is being read, also logging it to a file -class Reader: - def __init__(self, buf, filename = None): - self.buf = buf - if filename is not None: - self.log = open(filename, 'w') - else: - self.log = None - - def __iter__(self): - return self - - def next(self): - return self.readline() - - __next__ = next - - def readline(self): - line = self.buf.readline() - - if not line: - raise StopIteration - - # Log to a file if one is present - if self.log is not None: - self.log.write(line) - self.log.flush() - - # Return to the caller - return line - -def output_results(results, mvdelim = '\n', output = sys.stdout): - """Given a list of dictionaries, each representing - a single result, and an optional list of fields, - output those results to stdout for consumption by the - Splunk pipeline""" - - # We collect all the unique field names, as well as - # convert all multivalue keys to the right form - fields = set() - for result in results: - for key in result.keys(): - if(isinstance(result[key], list)): - result['__mv_' + key] = encode_mv(result[key]) - result[key] = mvdelim.join(result[key]) - fields.update(list(result.keys())) - - # convert the fields into a list and create a CSV writer - # to output to stdout - fields = sorted(list(fields)) - - writer = csv.DictWriter(output, fields) - - # Write out the fields, and then the actual results - writer.writerow(dict(list(zip(fields, fields)))) - writer.writerows(results) - -def read_input(buf, has_header = True): - """Read the input from the given buffer (or stdin if no buffer) - is supplied. An optional header may be present as well""" - - # Use stdin if there is no supplied buffer - if buf is None: - buf = sys.stdin - - # Attempt to read a header if necessary - header = {} - if has_header: - # Until we get a blank line, read "attr:val" lines, - # setting the values in 'header' - last_attr = None - while True: - line = buf.readline() - - # remove lastcharacter (which is a newline) - line = line[:-1] - - # When we encounter a newline, we are done with the header - if len(line) == 0: - break - - colon = line.find(':') - - # If we can't find a colon, then it might be that we are - # on a new line, and it belongs to the previous attribute - if colon < 0: - if last_attr: - header[last_attr] = header[last_attr] + '\n' + urllib.parse.unquote(line) - else: - continue - - # extract it and set value in settings - last_attr = attr = line[:colon] - val = urllib.parse.unquote(line[colon+1:]) - header[attr] = val - - return buf, header - -def encode_mv(vals): - """For multivalues, values are wrapped in '$' and separated using ';' - Literal '$' values are represented with '$$'""" - s = "" - for val in vals: - val = val.replace('$', '$$') - if len(s) > 0: - s += ';' - s += '$' + val + '$' - - return s - -def main(argv): - stdin_wrapper = Reader(sys.stdin) - buf, settings = read_input(stdin_wrapper, has_header = True) - events = csv.DictReader(buf) - - results = [] - - for event in events: - # For each event, we read in the raw event data - raw = StringIO(event["_raw"]) - top_output = csv.DictReader(raw, delimiter = ' ', skipinitialspace = True) - - # And then, for each row of the output of the 'top' command - # (where each row represents a single process), we look at the - # owning user of that process. - usercounts = {} - for row in top_output: - user = row["USER"] - user = user if not user.startswith('_') else user[1:] - - usercount = 0 - if user in usercounts: - usercount = usercounts[user] - - usercount += 1 - usercounts[user] = usercount - - results.append(usercounts) - - # And output it to the next stage of the pipeline - output_results(results) - - -if __name__ == "__main__": - try: - main(sys.argv) - except Exception: - import traceback - traceback.print_exc(file=sys.stdout) diff --git a/examples/custom_search/default/app.conf b/examples/custom_search/default/app.conf deleted file mode 100644 index 3db1dd146..000000000 --- a/examples/custom_search/default/app.conf +++ /dev/null @@ -1,13 +0,0 @@ -# -# Splunk app configuration file -# - -[ui] -is_visible = 1 -label = custom_search - -[launcher] -author = Splunk -description = -version = 1.0 - diff --git a/examples/custom_search/default/data/ui/nav/default.xml b/examples/custom_search/default/data/ui/nav/default.xml deleted file mode 100644 index c2128a6f3..000000000 --- a/examples/custom_search/default/data/ui/nav/default.xml +++ /dev/null @@ -1,18 +0,0 @@ - diff --git a/examples/custom_search/default/data/ui/views/README b/examples/custom_search/default/data/ui/views/README deleted file mode 100644 index 6cf74f0bc..000000000 --- a/examples/custom_search/default/data/ui/views/README +++ /dev/null @@ -1 +0,0 @@ -Add all the views that your app needs in this directory diff --git a/examples/custom_search/local/app.conf b/examples/custom_search/local/app.conf deleted file mode 100644 index 78666a91e..000000000 --- a/examples/custom_search/local/app.conf +++ /dev/null @@ -1,3 +0,0 @@ -[ui] - -[launcher] diff --git a/examples/custom_search/local/commands.conf b/examples/custom_search/local/commands.conf deleted file mode 100644 index 891c6af09..000000000 --- a/examples/custom_search/local/commands.conf +++ /dev/null @@ -1,7 +0,0 @@ -[usercount] -filename = usercount.py -streaming = false -retainsevents = false -overrides_timeorder = true -enableheader = true -passauth = true \ No newline at end of file diff --git a/examples/custom_search/metadata/default.meta b/examples/custom_search/metadata/default.meta deleted file mode 100644 index ad9ff9361..000000000 --- a/examples/custom_search/metadata/default.meta +++ /dev/null @@ -1,29 +0,0 @@ - -# Application-level permissions - -[] -access = read : [ * ], write : [ admin, power ] - -### EVENT TYPES - -[eventtypes] -export = system - - -### PROPS - -[props] -export = system - - -### TRANSFORMS - -[transforms] -export = system - - -### VIEWSTATES: even normal users should be able to create shared viewstates - -[viewstates] -access = read : [ * ], write : [ * ] -export = system diff --git a/examples/custom_search/metadata/local.meta b/examples/custom_search/metadata/local.meta deleted file mode 100644 index f62d553a7..000000000 --- a/examples/custom_search/metadata/local.meta +++ /dev/null @@ -1,7 +0,0 @@ -[app/ui] -owner = itay -version = 4.2.2 - -[app/launcher] -owner = itay -version = 4.2.2 diff --git a/tests/test_examples.py b/tests/test_examples.py index fe71a1e4b..059d54645 100755 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -253,67 +253,6 @@ def test_upload(self): "upload.py --help", "upload.py --index=sdk-tests %s" % file_to_upload) - # The following tests are for the custom_search examples. The way - # the tests work mirrors how Splunk would invoke them: they pipe in - # a known good input file into the custom search python file, and then - # compare the resulting output file to a known good one. - def test_custom_search(self): - - def test_custom_search_command(script, input_path, baseline_path): - output_base, _ = os.path.splitext(input_path) - output_path = output_base + ".out" - output_file = io.open(output_path, 'bw') - - input_file = io.open(input_path, 'br') - - # Execute the command - result = run(script, stdin=input_file, stdout=output_file) - self.assertEquals(result, 0) - - input_file.close() - output_file.close() - - # Make sure the test output matches the baseline - baseline_file = io.open(baseline_path, 'br') - baseline = baseline_file.read().decode('utf-8') - - output_file = io.open(output_path, 'br') - output = output_file.read().decode('utf-8') - - # TODO: DVPL-6700: Rewrite this test so that it is insensitive to ties in score - - message = "%s: %s != %s" % (script, output_file.name, baseline_file.name) - check_multiline(self, baseline, output, message) - - # Cleanup - baseline_file.close() - output_file.close() - os.remove(output_path) - - custom_searches = [ - { - "script": "custom_search/bin/usercount.py", - "input": "../tests/data/custom_search/usercount.in", - "baseline": "../tests/data/custom_search/usercount.baseline" - }, - { - "script": "twitted/twitted/bin/hashtags.py", - "input": "../tests/data/custom_search/hashtags.in", - "baseline": "../tests/data/custom_search/hashtags.baseline" - }, - { - "script": "twitted/twitted/bin/tophashtags.py", - "input": "../tests/data/custom_search/tophashtags.in", - "baseline": "../tests/data/custom_search/tophashtags.baseline" - } - ] - - for custom_search in custom_searches: - test_custom_search_command( - custom_search['script'], - custom_search['input'], - custom_search['baseline']) - # The following tests are for the Analytics example def test_analytics(self): # We have to add the current path to the PYTHONPATH, From f489b05aa826d74f9bd485951233fef4b3e03b00 Mon Sep 17 00:00:00 2001 From: Eric Cheng Date: Wed, 30 Jan 2019 13:06:15 -0800 Subject: [PATCH 14/37] Update searchcommands app example (#240) searchcommands_app example now packages splunklib under lib folder instead of bin folder based on best practice --- .gitignore | 3 ++- examples/searchcommands_app/README.md | 8 ++++---- .../searchcommands_app/package/bin/countmatches.py | 4 +++- examples/searchcommands_app/package/bin/filter.py | 6 +++--- .../searchcommands_app/package/bin/generatehello.py | 8 +++++--- .../searchcommands_app/package/bin/generatetext.py | 6 ++++-- .../package/bin/pypygeneratetext.py | 7 ++++--- examples/searchcommands_app/package/bin/simulate.py | 8 +++++--- examples/searchcommands_app/package/bin/sum.py | 4 +++- examples/searchcommands_app/setup.py | 13 ++++++------- tests/searchcommands/test_searchcommands_app.py | 6 ++++-- 11 files changed, 43 insertions(+), 30 deletions(-) diff --git a/.gitignore b/.gitignore index 853591131..d04e26353 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ .coverage .coverage.* .python-version +.vscode __stdout__ docs/_build build/ @@ -24,7 +25,7 @@ tests/searchcommands/data/app/app.log splunk_sdk.egg-info/ dist/ examples/searchcommands_app/package/default/commands.conf -examples/searchcommands_app/package/bin/packages +examples/searchcommands_app/package/lib/splunklib tests/searchcommands/apps/app_with_logging_configuration/*.log *.observed venv/ \ No newline at end of file diff --git a/examples/searchcommands_app/README.md b/examples/searchcommands_app/README.md index 763022f97..61bb4673a 100644 --- a/examples/searchcommands_app/README.md +++ b/examples/searchcommands_app/README.md @@ -15,14 +15,14 @@ The app is tested on Splunk 5 and 6. Here is its manifest: ``` ├── bin -│ ├── splunklib -│ │ └── searchcommands ....... splunklib.searchcommands module │   ├── countmatches.py .......... CountMatchesCommand implementation │ ├── generatetext.py .......... GenerateTextCommand implementation │ ├── pypygeneratetext.py ...... Executes generatetext.py with PyPy │ ├── simulate.py .............. SimulateCommand implementation -│ ├── sum.py ................... SumCommand implementation -│   └── +│ └── sum.py ................... SumCommand implementation +├── lib +| └── splunklib +│ └── searchcommands ....... splunklib.searchcommands module ├── default │ ├── data │ │   └── ui diff --git a/examples/searchcommands_app/package/bin/countmatches.py b/examples/searchcommands_app/package/bin/countmatches.py index 03231d790..c8049f665 100755 --- a/examples/searchcommands_app/package/bin/countmatches.py +++ b/examples/searchcommands_app/package/bin/countmatches.py @@ -17,9 +17,11 @@ from __future__ import absolute_import, division, print_function, unicode_literals import app +import os,sys +splunkhome = os.environ['SPLUNK_HOME'] +sys.path.append(os.path.join(splunkhome, 'etc', 'apps', 'searchcommands_app', 'lib')) from splunklib.searchcommands import dispatch, StreamingCommand, Configuration, Option, validators -import sys from splunklib import six diff --git a/examples/searchcommands_app/package/bin/filter.py b/examples/searchcommands_app/package/bin/filter.py index 36c719eff..f85615c17 100755 --- a/examples/searchcommands_app/package/bin/filter.py +++ b/examples/searchcommands_app/package/bin/filter.py @@ -17,13 +17,13 @@ from __future__ import absolute_import, division, print_function, unicode_literals import app +import os,sys +splunkhome = os.environ['SPLUNK_HOME'] +sys.path.append(os.path.join(splunkhome, 'etc', 'apps', 'searchcommands_app', 'lib')) from splunklib.searchcommands import dispatch, EventingCommand, Configuration, Option from splunklib.searchcommands.validators import Code -import sys - - @Configuration() class FilterCommand(EventingCommand): """ Filters, augments, and updates records on the events stream. diff --git a/examples/searchcommands_app/package/bin/generatehello.py b/examples/searchcommands_app/package/bin/generatehello.py index c8bcc506c..b61e8d8d6 100755 --- a/examples/searchcommands_app/package/bin/generatehello.py +++ b/examples/searchcommands_app/package/bin/generatehello.py @@ -17,11 +17,13 @@ from __future__ import absolute_import, division, print_function, unicode_literals import app +import os,sys +import time +splunkhome = os.environ['SPLUNK_HOME'] +sys.path.append(os.path.join(splunkhome, 'etc', 'apps', 'searchcommands_app', 'lib')) from splunklib.searchcommands import dispatch, GeneratingCommand, Configuration, Option, validators -import sys -import time -from six.moves import range +from splunklib.six.moves import range @Configuration() diff --git a/examples/searchcommands_app/package/bin/generatetext.py b/examples/searchcommands_app/package/bin/generatetext.py index 34411fc49..f999f11b1 100755 --- a/examples/searchcommands_app/package/bin/generatetext.py +++ b/examples/searchcommands_app/package/bin/generatetext.py @@ -17,10 +17,12 @@ from __future__ import absolute_import, division, print_function, unicode_literals import app +import os,sys +import time +splunkhome = os.environ['SPLUNK_HOME'] +sys.path.append(os.path.join(splunkhome, 'etc', 'apps', 'searchcommands_app', 'lib')) from splunklib.searchcommands import dispatch, GeneratingCommand, Configuration, Option, validators -import sys -import time from splunklib import six from splunklib.six.moves import range diff --git a/examples/searchcommands_app/package/bin/pypygeneratetext.py b/examples/searchcommands_app/package/bin/pypygeneratetext.py index aec19e2f9..adf2c3b35 100755 --- a/examples/searchcommands_app/package/bin/pypygeneratetext.py +++ b/examples/searchcommands_app/package/bin/pypygeneratetext.py @@ -63,11 +63,12 @@ from __future__ import absolute_import, division, print_function, unicode_literals import app - -from splunklib.searchcommands import app_root, execute +import sys from os import environ, path -import sys +splunkhome = environ['SPLUNK_HOME'] +sys.path.append(path.join(splunkhome, 'etc', 'apps', 'searchcommands_app', 'lib')) +from splunklib.searchcommands import app_root, execute pypy_argv = ['pypy', path.join(app_root, 'bin', 'generatetext.py')] + sys.argv[1:] pypy_environ = dict(environ) diff --git a/examples/searchcommands_app/package/bin/simulate.py b/examples/searchcommands_app/package/bin/simulate.py index fb616938b..31d68423f 100755 --- a/examples/searchcommands_app/package/bin/simulate.py +++ b/examples/searchcommands_app/package/bin/simulate.py @@ -17,13 +17,15 @@ from __future__ import absolute_import, division, print_function, unicode_literals import app - -from splunklib.searchcommands import dispatch, GeneratingCommand, Configuration, Option, validators import random import csv -import sys +import os,sys import time +splunkhome = os.environ['SPLUNK_HOME'] +sys.path.append(os.path.join(splunkhome, 'etc', 'apps', 'searchcommands_app', 'lib')) +from splunklib.searchcommands import dispatch, GeneratingCommand, Configuration, Option, validators + @Configuration() class SimulateCommand(GeneratingCommand): diff --git a/examples/searchcommands_app/package/bin/sum.py b/examples/searchcommands_app/package/bin/sum.py index 425f9dd45..a714699db 100755 --- a/examples/searchcommands_app/package/bin/sum.py +++ b/examples/searchcommands_app/package/bin/sum.py @@ -17,9 +17,11 @@ from __future__ import absolute_import, division, print_function, unicode_literals import app +import os,sys +splunkhome = os.environ['SPLUNK_HOME'] +sys.path.append(os.path.join(splunkhome, 'etc', 'apps', 'searchcommands_app', 'lib')) from splunklib.searchcommands import dispatch, ReportingCommand, Configuration, Option, validators -import sys @Configuration(requires_preop=True) diff --git a/examples/searchcommands_app/setup.py b/examples/searchcommands_app/setup.py index ab8d6cac4..7fb650d6a 100755 --- a/examples/searchcommands_app/setup.py +++ b/examples/searchcommands_app/setup.py @@ -88,7 +88,7 @@ def install_packages(app_root, distribution): if not requires: return - target = os.path.join(app_root, 'bin', 'packages') + target = os.path.join(app_root, 'lib', 'packages') if not os.path.isdir(target): os.mkdir(target) @@ -323,7 +323,7 @@ def run(self): message = 'Cannot create a link at "{}" because a file by that name already exists.'.format(target) raise SystemError(message) - packages = os.path.join(self.app_source, 'bin', 'packages') + packages = os.path.join(self.app_source, 'lib') if not os.path.isdir(packages): os.mkdir(packages) @@ -454,13 +454,12 @@ def run(self): 'Topic :: System :: Logging', 'Topic :: System :: Monitoring'], packages=[ - 'bin.packages.splunklib', 'bin.packages.splunklib.searchcommands' + 'lib.splunklib', 'lib.splunklib.searchcommands' ], package_dir={ - 'bin': os.path.join('package', 'bin'), - 'bin.packages': os.path.join('package', 'bin', 'packages'), - 'bin.packages.splunklib': os.path.join('..', '..', 'splunklib'), - 'bin.packages.splunklib.searchcommands': os.path.join('..', '..', 'splunklib', 'searchcommands') + 'lib': os.path.join('package', 'lib'), + 'lib.splunklib': os.path.join('..', '..', 'splunklib'), + 'lib.splunklib.searchcommands': os.path.join('..', '..', 'splunklib', 'searchcommands') }, package_data={ 'bin': [ diff --git a/tests/searchcommands/test_searchcommands_app.py b/tests/searchcommands/test_searchcommands_app.py index 0621f0310..2c4d09562 100755 --- a/tests/searchcommands/test_searchcommands_app.py +++ b/tests/searchcommands/test_searchcommands_app.py @@ -43,6 +43,7 @@ import csv import io import os +import sys try: from tests.searchcommands import project_root @@ -156,7 +157,6 @@ def setUp(self): TestCase.setUp(self) def test_countmatches_as_unit(self): - expected, output, errors, exit_status = self._run_command('countmatches', action='getinfo', protocol=1) self.assertEqual(0, exit_status, msg=six.text_type(errors)) self.assertEqual('', errors) @@ -415,7 +415,9 @@ def _run_command(self, name, action=None, phase=None, protocol=2): break ofile.write(b) with io.open(uncompressed_file, 'rb') as ifile: - process = Popen(recording.get_args(command), stdin=ifile, stderr=PIPE, stdout=PIPE) + env = os.environ.copy() + env['PYTHONPATH'] = ":".join(sys.path) + process = Popen(recording.get_args(command), stdin=ifile, stderr=PIPE, stdout=PIPE, env=env) output, errors = process.communicate() with io.open(recording.output_file, 'rb') as ifile: expected = ifile.read() From 09c221a7e40dfcf671a491d98eae6f01118abf78 Mon Sep 17 00:00:00 2001 From: Eric Cheng Date: Fri, 1 Feb 2019 15:28:49 -0800 Subject: [PATCH 15/37] fix tests on windows (#241) fix searchcommands_app example tests so that it'll work on windows machine --- tests/searchcommands/test_searchcommands_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/searchcommands/test_searchcommands_app.py b/tests/searchcommands/test_searchcommands_app.py index 2c4d09562..8945c3644 100755 --- a/tests/searchcommands/test_searchcommands_app.py +++ b/tests/searchcommands/test_searchcommands_app.py @@ -416,7 +416,7 @@ def _run_command(self, name, action=None, phase=None, protocol=2): ofile.write(b) with io.open(uncompressed_file, 'rb') as ifile: env = os.environ.copy() - env['PYTHONPATH'] = ":".join(sys.path) + env['PYTHONPATH'] = os.pathsep.join(sys.path) process = Popen(recording.get_args(command), stdin=ifile, stderr=PIPE, stdout=PIPE, env=env) output, errors = process.communicate() with io.open(recording.output_file, 'rb') as ifile: From 6d71c2c40a8ea717330c44fd7c62245af2a142a0 Mon Sep 17 00:00:00 2001 From: taylorcole44 <44789670+taylorcole44@users.noreply.github.com> Date: Thu, 7 Feb 2019 14:34:56 -0800 Subject: [PATCH 16/37] Update README.md --- examples/searchcommands_app/README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/examples/searchcommands_app/README.md b/examples/searchcommands_app/README.md index 61bb4673a..88a739841 100644 --- a/examples/searchcommands_app/README.md +++ b/examples/searchcommands_app/README.md @@ -1,15 +1,17 @@ splunk-sdk-python searchcommands_app example ============================================= -This app provides several examples of custom search commands which illustrate each of the base types: +This app provides several examples of custom search commands that illustrate each of the base command types: Command | Type | Description :---------------- |:-----------|:------------------------------------------------------------------------------------------- countmatches | Streaming | Counts the number of non-overlapping matches to a regular expression in a set of fields. generatetext | Generating | Generates a specified number of events containing a specified text string. - pypygeneratetext | | Executes generatetext with PyPy - simulate | Generating | Generates a sequence of events drawn from a csv file using repeated random sampling with replacement - sum | Reporting | Adds all the numbers in a set of fields. + pypygeneratetext | Generating | Executes generatetext with the string 'PyPy'. + simulate | Generating | Generates a sequence of events drawn from a csv file using repeated random sampling with replacement. + generatehello | Generating | Generates a specified number of events containing the text string 'hello'. + sum | Reporting | Adds all of the numbers in a set of fields. + filter | Eventing | Filters records from the events stream based on user-specified criteria. The app is tested on Splunk 5 and 6. Here is its manifest: @@ -59,15 +61,15 @@ The app is tested on Splunk 5 and 6. Here is its manifest: The tarball is build as build/searchcommands_app-1.5.0-private.tar.gz. -+ And then (re)start Splunk so that the app is recognized. ++ Then (re)start Splunk so that the app is recognized. ## Dashboards and Searches -+ TODO: Add saved search(es) for each example ++ TODO: Add saved search(es) for each example. ### Searches -+ TODO: Describe saved searches ++ TODO: Describe saved searches. ## License From b7f740003489b0365c1adb2a5a32e54bbbac09d7 Mon Sep 17 00:00:00 2001 From: taylorcole44 <44789670+taylorcole44@users.noreply.github.com> Date: Thu, 7 Feb 2019 14:45:41 -0800 Subject: [PATCH 17/37] Update logging.conf.spec --- .../package/README/logging.conf.spec | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/searchcommands_app/package/README/logging.conf.spec b/examples/searchcommands_app/package/README/logging.conf.spec index 3f134757b..c9b93118a 100644 --- a/examples/searchcommands_app/package/README/logging.conf.spec +++ b/examples/searchcommands_app/package/README/logging.conf.spec @@ -3,8 +3,8 @@ # # [Configuration file format](https://docs.python.org/2/library/logging.config.html#configuration-file-format) # -# This file must contain sections called [loggers], [handlers] and [formatters] which identify by name the entities of -# each type which are defined in the file. For each such entity, there is a separate section which identifies how that +# This file must contain sections called [loggers], [handlers] and [formatters] that identify by name the entities of +# each type that are defined in the file. For each such entity, there is a separate section that identifies how that # entity is configured. Thus, for a logger named log01 in the [loggers] section, the relevant configuration details are # held in a section [logger_log01]. Similarly, a handler called hand01 in the [handlers] section will have its # configuration held in a section called [handler_hand01], while a formatter called form01 in the [formatters] section @@ -24,7 +24,7 @@ keys = * The root logger must specify a level and a list of handlers. level = [critical|error|warning|info|debug|notset] - * Can be one of debug, info, warning, error, critical or notset. For the root logger only, notset means that all + * Can be one of debug, info, warning, error, critical, or notset. For the root logger only, notset means that all * messages will be logged. Level values are evaluated in the context of the logging package’s namespace. * Defaults to warning. @@ -43,7 +43,7 @@ qualname = level = [critical|error|warning|info|debug|notset] * Can be one of debug, info, warning, error, critical or notset. For the root logger only, notset means that all * messages will be logged. Level values are evaluated in the context of the logging package’s namespace. - * Defaults to warning + * Defaults to warning. handlers = * A comma-separated list of handler names, which must appear in the [handlers] section. These names must appear in @@ -57,7 +57,7 @@ propagate = [0|1] [handlers] * Specifies a list of handler keys. - * See [logging.handlers](https://docs.python.org/2/library/logging.handlers.html) + * See [logging.handlers](https://docs.python.org/2/library/logging.handlers.html). keys = * A comma-separated list of handlers keys. Each key must have a corresponding [handler_] section in the @@ -77,7 +77,7 @@ class = level = [critical|error|warning|info|debug|notset] * Can be one of debug, info, warning, error, critical or notset. This value is interpreted as for loggers, and - * notset is taken to mean, "log everything." + * notset is taken to mean, "log everything". formatter = * Specifies the key name of the formatter for this handler. If a name is specified, it must appear in the @@ -86,7 +86,7 @@ formatter = [formatters] * Specifies a list of formatter keys. - * See [logging.formatters](https://docs.python.org/2/howto/logging.html#formatters) + * See [logging.formatters](https://docs.python.org/2/howto/logging.html#formatters). keys = * A comma-separated list of formatter keys. Each key must have a corresponding [formatter_] section in the From 5f637f09db5c3a76549e3531e0bc90db28f6d570 Mon Sep 17 00:00:00 2001 From: taylorcole44 <44789670+taylorcole44@users.noreply.github.com> Date: Fri, 8 Feb 2019 11:52:04 -0800 Subject: [PATCH 18/37] Update README.md --- examples/searchcommands_app/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/searchcommands_app/README.md b/examples/searchcommands_app/README.md index 88a739841..ac6d1be09 100644 --- a/examples/searchcommands_app/README.md +++ b/examples/searchcommands_app/README.md @@ -7,7 +7,7 @@ This app provides several examples of custom search commands that illustrate eac :---------------- |:-----------|:------------------------------------------------------------------------------------------- countmatches | Streaming | Counts the number of non-overlapping matches to a regular expression in a set of fields. generatetext | Generating | Generates a specified number of events containing a specified text string. - pypygeneratetext | Generating | Executes generatetext with the string 'PyPy'. + pypygeneratetext | Generating | Runs generatetext with the string 'PyPy'. simulate | Generating | Generates a sequence of events drawn from a csv file using repeated random sampling with replacement. generatehello | Generating | Generates a specified number of events containing the text string 'hello'. sum | Reporting | Adds all of the numbers in a set of fields. @@ -19,7 +19,7 @@ The app is tested on Splunk 5 and 6. Here is its manifest: ├── bin │   ├── countmatches.py .......... CountMatchesCommand implementation │ ├── generatetext.py .......... GenerateTextCommand implementation -│ ├── pypygeneratetext.py ...... Executes generatetext.py with PyPy +│ ├── pypygeneratetext.py ...... Runs generatetext.py with PyPy │ ├── simulate.py .............. SimulateCommand implementation │ └── sum.py ................... SumCommand implementation ├── lib From 55aa2dd6e012afb9134795298ec8c0e67a1e5327 Mon Sep 17 00:00:00 2001 From: Lowell Alleman Date: Thu, 14 Feb 2019 17:15:43 -0500 Subject: [PATCH 19/37] Fix inline examples --- splunklib/binding.py | 1 + splunklib/client.py | 6 ++-- .../modularinput/validation_definition.py | 32 ++++++++++--------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/splunklib/binding.py b/splunklib/binding.py index 3fe7c8495..62efd03f2 100644 --- a/splunklib/binding.py +++ b/splunklib/binding.py @@ -80,6 +80,7 @@ def _parse_cookies(cookie_str, dictionary): then updates the the dictionary with any key-value pairs found. **Example**:: + dictionary = {} _parse_cookies('my=value', dictionary) # Now the following is True diff --git a/splunklib/client.py b/splunklib/client.py index 1e624ba42..a39261206 100644 --- a/splunklib/client.py +++ b/splunklib/client.py @@ -1200,7 +1200,7 @@ def __getitem__(self, key): :raises ValueError: Raised if no namespace is specified and *key* does not refer to a unique name. - *Example*:: + **Example**:: s = client.connect(...) saved_searches = s.saved_searches @@ -1636,9 +1636,9 @@ def get(self, name="", owner=None, app=None, sharing=None, **query): :rtype: ``dict`` with keys ``body``, ``headers``, ``reason``, and ``status`` - Example: + **Example**:: - import splunklib.client + import splunklib.client s = client.service(...) saved_searches = s.saved_searches saved_searches.get("my/saved/search") == \\ diff --git a/splunklib/modularinput/validation_definition.py b/splunklib/modularinput/validation_definition.py index 8904e40b1..ac9a89f53 100755 --- a/splunklib/modularinput/validation_definition.py +++ b/splunklib/modularinput/validation_definition.py @@ -28,7 +28,7 @@ class ValidationDefinition(object): **Example**:: - ``v = ValidationDefinition()`` + v = ValidationDefinition() """ def __init__(self): @@ -46,20 +46,22 @@ def parse(stream): The XML typically will look like this: - ```` - `` myHost`` - `` https://127.0.0.1:8089`` - `` 123102983109283019283`` - `` /opt/splunk/var/lib/splunk/modinputs`` - `` `` - `` value1`` - `` `` - `` value2`` - `` value3`` - `` value4`` - `` `` - `` `` - ```` + .. code-block:: xml + + + myHost + https://127.0.0.1:8089 + 123102983109283019283 + /opt/splunk/var/lib/splunk/modinputs + + value1 + + value2 + value3 + value4 + + + :param stream: ``Stream`` containing XML to parse. :return definition: A ``ValidationDefinition`` object. From 1c78030856b0a4f3df14a884978406964234c6fe Mon Sep 17 00:00:00 2001 From: Lowell Alleman Date: Thu, 14 Feb 2019 17:19:54 -0500 Subject: [PATCH 20/37] Fix param indentation Continuation lines after a :param: must be indented. --- splunklib/modularinput/argument.py | 4 ++-- splunklib/modularinput/script.py | 6 +++--- splunklib/searchcommands/external_search_command.py | 8 ++++---- splunklib/searchcommands/search_command.py | 6 +++--- splunklib/searchcommands/validators.py | 6 +++--- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/splunklib/modularinput/argument.py b/splunklib/modularinput/argument.py index 4c4b3c82c..04214d16d 100755 --- a/splunklib/modularinput/argument.py +++ b/splunklib/modularinput/argument.py @@ -54,9 +54,9 @@ def __init__(self, name, description=None, validation=None, :param name: ``string``, identifier for this argument in Splunk. :param description: ``string``, human-readable description of the argument. :param validation: ``string`` specifying how the argument should be validated, if using internal validation. - If using external validation, this will be ignored. + If using external validation, this will be ignored. :param data_type: ``string``, data type of this field; use the class constants. - "data_type_boolean", "data_type_number", or "data_type_string". + "data_type_boolean", "data_type_number", or "data_type_string". :param required_on_edit: ``Boolean``, whether this arg is required when editing an existing modular input of this kind. :param required_on_create: ``Boolean``, whether this arg is required when creating a modular input of this kind. :param title: ``String``, a human-readable title for the argument. diff --git a/splunklib/modularinput/script.py b/splunklib/modularinput/script.py index 040a07d9f..a254dfa25 100755 --- a/splunklib/modularinput/script.py +++ b/splunklib/modularinput/script.py @@ -118,9 +118,9 @@ def service(self): available as soon as the :code:`Script.stream_events` method is called. - :return: :class:splunklib.client.Service. A value of None is returned, - if you call this method before the :code:`Script.stream_events` method - is called. + :return: :class:`splunklib.client.Service`. A value of None is returned, + if you call this method before the :code:`Script.stream_events` method + is called. """ if self._service is not None: diff --git a/splunklib/searchcommands/external_search_command.py b/splunklib/searchcommands/external_search_command.py index 2c4ce50b7..c2306241e 100644 --- a/splunklib/searchcommands/external_search_command.py +++ b/splunklib/searchcommands/external_search_command.py @@ -105,13 +105,13 @@ def _execute(path, argv=None, environ=None): :param argv: Argument list. :type argv: list or tuple - The arguments to the child process should start with the name of the command being run, but this is not - enforced. A value of :const:`None` specifies that the base name of path name :param:`path` should be used. + The arguments to the child process should start with the name of the command being run, but this is not + enforced. A value of :const:`None` specifies that the base name of path name :param:`path` should be used. :param environ: A mapping which is used to define the environment variables for the new process. :type environ: dict or None. - This mapping is used instead of the current process’s environment. A value of :const:`None` specifies that - the :data:`os.environ` mapping should be used. + This mapping is used instead of the current process’s environment. A value of :const:`None` specifies that + the :data:`os.environ` mapping should be used. :return: None diff --git a/splunklib/searchcommands/search_command.py b/splunklib/searchcommands/search_command.py index 47918abb6..77291123d 100644 --- a/splunklib/searchcommands/search_command.py +++ b/splunklib/searchcommands/search_command.py @@ -256,7 +256,7 @@ def search_results_info(self): invocation. :return: Search results info:const:`None`, if the search results info file associated with the command - invocation is inaccessible. + invocation is inaccessible. :rtype: SearchResultsInfo or NoneType """ @@ -345,8 +345,8 @@ def service(self): :code:`requires_srinfo` setting is false by default. Hence, you must set it. :return: :class:`splunklib.client.Service`, if :code:`enableheader` and :code:`requires_srinfo` are both - :code:`true`. Otherwise, if either :code:`enableheader` or :code:`requires_srinfo` are :code:`false`, a value - of :code:`None` is returned. + :code:`true`. Otherwise, if either :code:`enableheader` or :code:`requires_srinfo` are :code:`false`, a value + of :code:`None` is returned. """ if self._service is not None: diff --git a/splunklib/searchcommands/validators.py b/splunklib/searchcommands/validators.py index 66329375c..dca4cf422 100644 --- a/splunklib/searchcommands/validators.py +++ b/splunklib/searchcommands/validators.py @@ -81,9 +81,9 @@ class Code(Validator): def __init__(self, mode='eval'): """ :param mode: Specifies what kind of code must be compiled; it can be :const:`'exec'`, if source consists of a - sequence of statements, :const:`'eval'`, if it consists of a single expression, or :const:`'single'` if it - consists of a single interactive statement. In the latter case, expression statements that evaluate to - something other than :const:`None` will be printed. + sequence of statements, :const:`'eval'`, if it consists of a single expression, or :const:`'single'` if it + consists of a single interactive statement. In the latter case, expression statements that evaluate to + something other than :const:`None` will be printed. :type mode: unicode or bytes """ From d2455613136bff381c98b9f3bc370d91797fd5a1 Mon Sep 17 00:00:00 2001 From: Lowell Alleman Date: Thu, 14 Feb 2019 17:24:35 -0500 Subject: [PATCH 21/37] Fix returns Return variable name shouldn't be included via :returns: --- splunklib/modularinput/scheme.py | 2 +- splunklib/modularinput/validation_definition.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/splunklib/modularinput/scheme.py b/splunklib/modularinput/scheme.py index ff4f97832..4104e4a3f 100755 --- a/splunklib/modularinput/scheme.py +++ b/splunklib/modularinput/scheme.py @@ -55,7 +55,7 @@ def add_argument(self, arg): def to_xml(self): """Creates an ``ET.Element`` representing self, then returns it. - :returns root, an ``ET.Element`` representing this scheme. + :returns: an ``ET.Element`` representing this scheme. """ root = ET.Element("scheme") diff --git a/splunklib/modularinput/validation_definition.py b/splunklib/modularinput/validation_definition.py index ac9a89f53..3bbe9760e 100755 --- a/splunklib/modularinput/validation_definition.py +++ b/splunklib/modularinput/validation_definition.py @@ -64,7 +64,7 @@ def parse(stream): :param stream: ``Stream`` containing XML to parse. - :return definition: A ``ValidationDefinition`` object. + :return: A ``ValidationDefinition`` object. """ From 862d295edf552928da8442425d3b9dbd3295e81e Mon Sep 17 00:00:00 2001 From: Lowell Alleman Date: Thu, 14 Feb 2019 17:26:29 -0500 Subject: [PATCH 22/37] Fixed up code-blocks Also fixed command.conf spelling of "retainsevents" (not "retains events") --- splunklib/searchcommands/decorators.py | 6 ++-- .../searchcommands/generating_command.py | 35 ++++++++++++------- splunklib/searchcommands/search_command.py | 5 +-- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/splunklib/searchcommands/decorators.py b/splunklib/searchcommands/decorators.py index b5e16064a..02c1326e8 100644 --- a/splunklib/searchcommands/decorators.py +++ b/splunklib/searchcommands/decorators.py @@ -229,8 +229,9 @@ class Option(property): Short form (recommended). When you are satisfied with built-in or custom validation behaviors. - .. code-block:: python + .. code-block:: python :linenos: + from splunklib.searchcommands.decorators import Option from splunklib.searchcommands.validators import Fieldname @@ -247,8 +248,9 @@ class Option(property): also provide a deleter. You must be prepared to accept a value of :const:`None` which indicates that your :code:`Option` is unset. - .. code-block:: python + .. code-block:: python :linenos: + from splunklib.searchcommands import Option @Option() diff --git a/splunklib/searchcommands/generating_command.py b/splunklib/searchcommands/generating_command.py index fd0585e4c..46858cbdd 100644 --- a/splunklib/searchcommands/generating_command.py +++ b/splunklib/searchcommands/generating_command.py @@ -92,9 +92,10 @@ class StreamingGeneratingCommand(GeneratingCommand) +==========+===================================================+===================================================+ | streams | 1. Add this line to your command's stanza in | 1. Add this configuration setting to your code: | | | | | - | | default/commands.conf. | .. code-block:: python | - | | .. code-block:: python | @Configuration(distributed=True) | - | | local = false | class SomeCommand(GeneratingCommand) | + | | default/commands.conf:: | .. code-block:: python | + | | | | + | | local = false | @Configuration(distributed=True) | + | | | class SomeCommand(GeneratingCommand) | | | | ... | | | 2. Restart splunk | | | | | 2. You are good to go; no need to restart Splunk | @@ -112,6 +113,7 @@ class StreamingGeneratingCommand(GeneratingCommand) | | settings to your command class: | setting to your command class: | | | | | | | .. code-block:: python | .. code-block:: python | + | | | | | | @Configuration( | @Configuration(type='events') | | | retainsevents=True, streaming=False) | class SomeCommand(GeneratingCommand) | | | class SomeCommand(GeneratingCommand) | ... | @@ -119,22 +121,25 @@ class StreamingGeneratingCommand(GeneratingCommand) | | | | | | Or add these lines to default/commands.conf: | | | | | | - | | .. code-block:: | | - | | retains events = true | | + | | .. code-block:: text | | + | | | | + | | retainsevents = true | | | | streaming = false | | +----------+---------------------------------------------------+---------------------------------------------------+ Configure your command class like this, if you wish to support both protocols: - .. code-block:: python + .. code-block:: python + @Configuration(type='events', retainsevents=True, streaming=False) class SomeCommand(GeneratingCommand) ... You might also consider adding these lines to commands.conf instead of adding them to your command class: - .. code-block:: python - retains events = false + .. code-block:: python + + retainsevents = false streaming = false Reporting Generating command @@ -149,28 +154,32 @@ class SomeCommand(GeneratingCommand) | | settings to your command class: | setting to your command class: | | | | | | | .. code-block:: python | .. code-block:: python | + | | | | | | @Configuration(retainsevents=False) | @Configuration(type='reporting') | | | class SomeCommand(GeneratingCommand) | class SomeCommand(GeneratingCommand) | | | ... | ... | | | | | | | Or add this lines to default/commands.conf: | | | | | | - | | .. code-block:: | | - | | retains events = false | | + | | .. code-block:: text | | + | | | | + | | retainsevents = false | | | | streaming = false | | +----------+---------------------------------------------------+---------------------------------------------------+ Configure your command class like this, if you wish to support both protocols: - .. code-block:: python + .. code-block:: python + @Configuration(type='reporting', streaming=False) class SomeCommand(GeneratingCommand) ... You might also consider adding these lines to commands.conf instead of adding them to your command class: - .. code-block:: python - retains events = false + .. code-block:: text + + retainsevents = false streaming = false """ diff --git a/splunklib/searchcommands/search_command.py b/splunklib/searchcommands/search_command.py index 77291123d..c3b309790 100644 --- a/splunklib/searchcommands/search_command.py +++ b/splunklib/searchcommands/search_command.py @@ -338,6 +338,7 @@ def service(self): specifying this pair of configuration settings in commands.conf: .. code-block:: python + enableheader = true requires_srinfo = true @@ -1080,7 +1081,7 @@ def dispatch(command_class, argv=sys.argv, input_file=sys.stdin, output_file=sys **Example** - .. code-block:: python + .. code-block:: python :linenos: #!/usr/bin/env python @@ -1096,7 +1097,7 @@ def stream(records): **Example** - .. code-block:: python + .. code-block:: python :linenos: from splunklib.searchcommands import dispatch, StreamingCommand, Configuration, Option, validators From 0c79366b8ab749bc5ef6967e75ebe5cef6f23303 Mon Sep 17 00:00:00 2001 From: Lowell Alleman Date: Thu, 14 Feb 2019 17:28:28 -0500 Subject: [PATCH 23/37] Misc doc fixes - Indentation issues - Removed ":param:`p`" references. Just using "``p``" instead. --- splunklib/searchcommands/__init__.py | 2 +- splunklib/searchcommands/search_command.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/splunklib/searchcommands/__init__.py b/splunklib/searchcommands/__init__.py index 12b14f32c..326e3e28e 100644 --- a/splunklib/searchcommands/__init__.py +++ b/splunklib/searchcommands/__init__.py @@ -30,7 +30,7 @@ field-name = ( "_" / alpha ) *( alpha / digit / "_" / "." / "-" ) It does not show that :code:`field-name` values may be comma-separated. This is because Splunk strips commas from - the command line. A search command will never see them. + the command line. A search command will never see them. 2. Search commands targeting versions of Splunk prior to 6.3 must be statically configured as follows: diff --git a/splunklib/searchcommands/search_command.py b/splunklib/searchcommands/search_command.py index c3b309790..64b06aba1 100644 --- a/splunklib/searchcommands/search_command.py +++ b/splunklib/searchcommands/search_command.py @@ -810,15 +810,15 @@ def write_metric(self, name, value): :param name: Name of the metric. :type name: basestring - :param value: A 4-tuple containing the value of metric :param:`name` where + :param value: A 4-tuple containing the value of metric ``name`` where value[0] = Elapsed seconds or :const:`None`. value[1] = Number of invocations or :const:`None`. value[2] = Input count or :const:`None`. value[3] = Output count or :const:`None`. - The :data:`SearchMetric` type provides a convenient encapsulation of :param:`value`. - The :data:`SearchMetric` type provides a convenient encapsulation of :param:`value`. + The :data:`SearchMetric` type provides a convenient encapsulation of ``value``. + The :data:`SearchMetric` type provides a convenient encapsulation of ``value``. :return: :const:`None`. From 01558e4f111fca98a7f58700f9bc3b57c28871a6 Mon Sep 17 00:00:00 2001 From: Lowell Alleman Date: Thu, 14 Feb 2019 17:32:39 -0500 Subject: [PATCH 24/37] Fixed missing function references - Moved splunklib.searchcommands.validators into it's own rst file. Not sure if this best. But it makes doc generation exceptions stop. :-) --- docs/searchcommands.rst | 7 ------- docs/searchcommandsvalidators.rst | 12 ++++++++++++ 2 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 docs/searchcommandsvalidators.rst diff --git a/docs/searchcommands.rst b/docs/searchcommands.rst index ece2e6a08..a620fbb84 100644 --- a/docs/searchcommands.rst +++ b/docs/searchcommands.rst @@ -80,10 +80,6 @@ splunklib.searchcommands :members: :inherited-members: -.. autoclass:: Fieldname - :members: - :inherited-members: - .. autoclass:: File :members: :inherited-members: @@ -100,6 +96,3 @@ splunklib.searchcommands :members: :inherited-members: -.. autoclass:: Validator - :members: - :inherited-members: diff --git a/docs/searchcommandsvalidators.rst b/docs/searchcommandsvalidators.rst new file mode 100644 index 000000000..8a54db264 --- /dev/null +++ b/docs/searchcommandsvalidators.rst @@ -0,0 +1,12 @@ +splunklib.searchcommands.validators +----------------------------------- + +.. automodule:: splunklib.searchcommands.validators + +.. autoclass:: Fieldname + :members: + :inherited-members: + +.. autoclass:: Validator + :members: + :inherited-members: From 31e7beaaf38b3d9982cd6324b82fffaf2279456d Mon Sep 17 00:00:00 2001 From: Lowell Alleman Date: Thu, 14 Feb 2019 17:38:49 -0500 Subject: [PATCH 25/37] Stop complaints about "Search command style guide" WARNING: Duplicate explicit target name: "search command style guide". Switched `<>`_ to `<>`__ to links. Frankly, I don't understand the difference, but not more complains. --- splunklib/searchcommands/__init__.py | 2 +- splunklib/searchcommands/decorators.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/splunklib/searchcommands/__init__.py b/splunklib/searchcommands/__init__.py index 326e3e28e..c56c510d5 100644 --- a/splunklib/searchcommands/__init__.py +++ b/splunklib/searchcommands/__init__.py @@ -134,7 +134,7 @@ .. topic:: References - 1. `Search command style guide `_ + 1. `Search command style guide `__ 2. `Commands.conf.spec `_ diff --git a/splunklib/searchcommands/decorators.py b/splunklib/searchcommands/decorators.py index 02c1326e8..36590a76b 100644 --- a/splunklib/searchcommands/decorators.py +++ b/splunklib/searchcommands/decorators.py @@ -36,7 +36,7 @@ class Configuration(object): variable to search command classes that don't have one. The :code:`name` is derived from the name of the class. By convention command class names end with the word "Command". To derive :code:`name` the word "Command" is removed from the end of the class name and then converted to lower case for conformance with the `Search command style guide - `_ + `__ """ def __init__(self, o=None, **kwargs): From d7c989f3099a8874f393708b3cd7bb338e3c3d99 Mon Sep 17 00:00:00 2001 From: Lowell Alleman Date: Thu, 14 Feb 2019 17:53:05 -0500 Subject: [PATCH 26/37] All docs to the toctree to fix sidebar This doesn't seem ideal, but the prior behavior was that the side bar is ONLY usable on the index. As soon as you click on the sub-page you can see it's name, and nothing else. This makes navigation painful, IMHO. This seems like an improvements, even if not great. --- docs/index.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index b04f51b1d..8f209468f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,6 +3,16 @@ For more information, see the `Splunk Developer Portal Date: Thu, 14 Feb 2019 17:57:45 -0500 Subject: [PATCH 27/37] Add ReadTheDocs badge to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 4e67795ff..e23767b76 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ [![Build Status](https://travis-ci.org/splunk/splunk-sdk-python.svg?branch=master)](https://travis-ci.org/splunk/splunk-sdk-python) +[![Documentation Status](https://readthedocs.org/projects/splunk-python-sdk/badge/?version=latest)](https://splunk-python-sdk.readthedocs.io/en/latest/?badge=latest) + # The Splunk Software Development Kit for Python #### Version 1.6.6 From 44eb11c3b09a0944c8fa5eb2f231612d615e862f Mon Sep 17 00:00:00 2001 From: Shakeel Mohamed Date: Mon, 25 Feb 2019 15:13:49 -0800 Subject: [PATCH 28/37] Performance improvement for updating or deleting input (#247) --- splunklib/client.py | 7 +++++++ tests/test_input.py | 16 ++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/splunklib/client.py b/splunklib/client.py index a39261206..3d9192c8b 100644 --- a/splunklib/client.py +++ b/splunklib/client.py @@ -463,6 +463,13 @@ def info(self): response = self.get("/services/server/info") return _filter_content(_load_atom(response, MATCH_ENTRY_CONTENT)) + def input(self, path, kind=None): + """Retrieves an input by path, and optionally kind. + + :return: A :class:`Input` object. + """ + return Input(self, path, kind=kind).refresh() + @property def inputs(self): """Returns the collection of inputs configured on this Splunk instance. diff --git a/tests/test_input.py b/tests/test_input.py index 7c7690e9a..14fceab74 100755 --- a/tests/test_input.py +++ b/tests/test_input.py @@ -16,8 +16,6 @@ from __future__ import absolute_import from __future__ import print_function -import unittest2 - from splunklib.binding import HTTPError from tests import testlib @@ -30,6 +28,7 @@ import splunklib.client as client + def highest_port(service, base_port, *kinds): """Find the first port >= base_port not in use by any input in kinds.""" highest_port = base_port @@ -38,6 +37,7 @@ def highest_port(service, base_port, *kinds): highest_port = max(port, highest_port) return highest_port + class TestTcpInputNameHandling(testlib.SDKTestCase): def setUp(self): super(TestTcpInputNameHandling, self).setUp() @@ -116,6 +116,7 @@ def test_update_restrictToHost_fails(self): lambda: boris.update(restrictToHost='hilda') ) + class TestRead(testlib.SDKTestCase): def test_read(self): inputs = self.service.inputs @@ -188,6 +189,7 @@ def test_oneshot_on_nonexistant_file(self): self.assertRaises(HTTPError, self.service.inputs.oneshot, name) + class TestInput(testlib.SDKTestCase): def setUp(self): super(TestInput, self).setUp() @@ -243,7 +245,6 @@ def test_lists_modular_inputs(self): input = inputs['abcd', 'test2'] self.assertEqual(input.field1, 'boris') - def test_create(self): inputs = self.service.inputs for entity in six.itervalues(self._test_entities): @@ -264,6 +265,13 @@ def test_read(self): self.assertEqual(this_entity.name, read_entity.name) self.assertEqual(this_entity.host, read_entity.host) + def test_read_indiviually(self): + tcp_input = self.service.input(self._test_entities['tcp'].path, + self._test_entities['tcp'].kind) + self.assertIsNotNone(tcp_input) + self.assertTrue('tcp', tcp_input.kind) + self.assertTrue(self._test_entities['tcp'].name, tcp_input.name) + def test_update(self): inputs = self.service.inputs for entity in six.itervalues(self._test_entities): @@ -273,7 +281,7 @@ def test_update(self): entity.refresh() self.assertEqual(entity.host, kwargs['host']) - @unittest2.skip('flaky') + @unittest.skip('flaky') def test_delete(self): inputs = self.service.inputs remaining = len(self._test_entities)-1 From 795a44eca42b52a2fc4fa8a90de3aaafbc6b0b6b Mon Sep 17 00:00:00 2001 From: Shakeel Mohamed Date: Tue, 26 Feb 2019 11:19:59 -0800 Subject: [PATCH 29/37] add path to search commands examples (#248) --- examples/searchcommands_template/bin/filter.py | 3 +++ examples/searchcommands_template/bin/generate.py | 3 +++ examples/searchcommands_template/bin/report.py | 3 +++ examples/searchcommands_template/bin/stream.py | 3 +++ 4 files changed, 12 insertions(+) diff --git a/examples/searchcommands_template/bin/filter.py b/examples/searchcommands_template/bin/filter.py index bc73ce6fa..194118af0 100644 --- a/examples/searchcommands_template/bin/filter.py +++ b/examples/searchcommands_template/bin/filter.py @@ -1,6 +1,9 @@ #!/usr/bin/env python import sys +import os + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib")) from splunklib.searchcommands import \ dispatch, StreamingCommand, Configuration, Option, validators diff --git a/examples/searchcommands_template/bin/generate.py b/examples/searchcommands_template/bin/generate.py index a57efa547..4622b3c95 100644 --- a/examples/searchcommands_template/bin/generate.py +++ b/examples/searchcommands_template/bin/generate.py @@ -1,6 +1,9 @@ #!/usr/bin/env python import sys +import os + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib")) from splunklib.searchcommands import \ dispatch, GeneratingCommand, Configuration, Option, validators diff --git a/examples/searchcommands_template/bin/report.py b/examples/searchcommands_template/bin/report.py index 03514dced..2d5269878 100644 --- a/examples/searchcommands_template/bin/report.py +++ b/examples/searchcommands_template/bin/report.py @@ -1,6 +1,9 @@ #!/usr/bin/env python import sys +import os + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib")) from splunklib.searchcommands import \ dispatch, ReportingCommand, Configuration, Option, validators diff --git a/examples/searchcommands_template/bin/stream.py b/examples/searchcommands_template/bin/stream.py index 2ab2b4c1d..aa7379038 100644 --- a/examples/searchcommands_template/bin/stream.py +++ b/examples/searchcommands_template/bin/stream.py @@ -1,6 +1,9 @@ #!/usr/bin/env python import sys +import os + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "lib")) from splunklib.searchcommands import \ dispatch, StreamingCommand, Configuration, Option, validators From b960561f8f4f9526d1848ea6e60f7a9e3c5f15bb Mon Sep 17 00:00:00 2001 From: Shakeel Mohamed Date: Wed, 27 Feb 2019 10:37:29 -0800 Subject: [PATCH 30/37] Remove merge local .conf to default in twitted example (#249) --- examples/twitted/twitted/default/app.conf | 2 +- examples/twitted/twitted/{local => default}/commands.conf | 0 examples/twitted/twitted/{local => default}/indexes.conf | 0 examples/twitted/twitted/{local => default}/inputs.conf | 0 examples/twitted/twitted/{local => default}/props.conf | 0 .../twitted/twitted/{local => default}/savedsearches.conf | 0 examples/twitted/twitted/{local => default}/transforms.conf | 0 examples/twitted/twitted/{local => default}/viewstates.conf | 0 examples/twitted/twitted/local/app.conf | 4 ---- 9 files changed, 1 insertion(+), 5 deletions(-) rename examples/twitted/twitted/{local => default}/commands.conf (100%) rename examples/twitted/twitted/{local => default}/indexes.conf (100%) rename examples/twitted/twitted/{local => default}/inputs.conf (100%) rename examples/twitted/twitted/{local => default}/props.conf (100%) rename examples/twitted/twitted/{local => default}/savedsearches.conf (100%) rename examples/twitted/twitted/{local => default}/transforms.conf (100%) rename examples/twitted/twitted/{local => default}/viewstates.conf (100%) delete mode 100644 examples/twitted/twitted/local/app.conf diff --git a/examples/twitted/twitted/default/app.conf b/examples/twitted/twitted/default/app.conf index f2371ba0c..4b55cee8d 100644 --- a/examples/twitted/twitted/default/app.conf +++ b/examples/twitted/twitted/default/app.conf @@ -9,5 +9,5 @@ label = twitted [launcher] author = description = -version = 1.0 +version = 1.2 diff --git a/examples/twitted/twitted/local/commands.conf b/examples/twitted/twitted/default/commands.conf similarity index 100% rename from examples/twitted/twitted/local/commands.conf rename to examples/twitted/twitted/default/commands.conf diff --git a/examples/twitted/twitted/local/indexes.conf b/examples/twitted/twitted/default/indexes.conf similarity index 100% rename from examples/twitted/twitted/local/indexes.conf rename to examples/twitted/twitted/default/indexes.conf diff --git a/examples/twitted/twitted/local/inputs.conf b/examples/twitted/twitted/default/inputs.conf similarity index 100% rename from examples/twitted/twitted/local/inputs.conf rename to examples/twitted/twitted/default/inputs.conf diff --git a/examples/twitted/twitted/local/props.conf b/examples/twitted/twitted/default/props.conf similarity index 100% rename from examples/twitted/twitted/local/props.conf rename to examples/twitted/twitted/default/props.conf diff --git a/examples/twitted/twitted/local/savedsearches.conf b/examples/twitted/twitted/default/savedsearches.conf similarity index 100% rename from examples/twitted/twitted/local/savedsearches.conf rename to examples/twitted/twitted/default/savedsearches.conf diff --git a/examples/twitted/twitted/local/transforms.conf b/examples/twitted/twitted/default/transforms.conf similarity index 100% rename from examples/twitted/twitted/local/transforms.conf rename to examples/twitted/twitted/default/transforms.conf diff --git a/examples/twitted/twitted/local/viewstates.conf b/examples/twitted/twitted/default/viewstates.conf similarity index 100% rename from examples/twitted/twitted/local/viewstates.conf rename to examples/twitted/twitted/default/viewstates.conf diff --git a/examples/twitted/twitted/local/app.conf b/examples/twitted/twitted/local/app.conf deleted file mode 100644 index 30970983e..000000000 --- a/examples/twitted/twitted/local/app.conf +++ /dev/null @@ -1,4 +0,0 @@ -[ui] - -[launcher] -version = 1.2 From 904461a3056929519a503c97600c9a74cf1f4f52 Mon Sep 17 00:00:00 2001 From: Eric Cheng Date: Thu, 28 Mar 2019 12:58:05 -0700 Subject: [PATCH 31/37] Add logging (#252) * add logging to custom search commands app examples --- .../searchcommands_app/package/bin/generatehello.py | 1 + .../searchcommands_app/package/bin/generatetext.py | 1 + .../searchcommands_app/package/default/logging.conf | 10 +++++----- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/examples/searchcommands_app/package/bin/generatehello.py b/examples/searchcommands_app/package/bin/generatehello.py index b61e8d8d6..572f6b740 100755 --- a/examples/searchcommands_app/package/bin/generatehello.py +++ b/examples/searchcommands_app/package/bin/generatehello.py @@ -32,6 +32,7 @@ class GenerateHelloCommand(GeneratingCommand): count = Option(require=True, validate=validators.Integer(0)) def generate(self): + self.logger.debug("Generating %s events" % self.count) for i in range(1, self.count + 1): text = 'Hello World %d' % i yield {'_time': time.time(), 'event_no': i, '_raw': text} diff --git a/examples/searchcommands_app/package/bin/generatetext.py b/examples/searchcommands_app/package/bin/generatetext.py index f999f11b1..8251e6571 100755 --- a/examples/searchcommands_app/package/bin/generatetext.py +++ b/examples/searchcommands_app/package/bin/generatetext.py @@ -35,6 +35,7 @@ class GenerateTextCommand(GeneratingCommand): def generate(self): text = self.text + self.logger.debug("Generating %d events with text %s" % (self.count, self.text)) for i in range(1, self.count + 1): yield {'_serial': i, '_time': time.time(), '_raw': six.text_type(i) + '. ' + text} diff --git a/examples/searchcommands_app/package/default/logging.conf b/examples/searchcommands_app/package/default/logging.conf index a772b75de..69b082534 100644 --- a/examples/searchcommands_app/package/default/logging.conf +++ b/examples/searchcommands_app/package/default/logging.conf @@ -24,15 +24,15 @@ propagate = 0 ; Default: 1 [logger_GenerateHelloCommand] qualname = GenerateHelloCommand -level = NOTSET ; Default: WARNING +level = DEBUG ; Default: WARNING handlers = app ; Default: stderr propagate = 0 ; Default: 1 [logger_GenerateTextCommand] qualname = GenerateTextCommand -level = NOTSET ; Default: WARNING -handlers = app ; Default: stderr -propagate = 0 ; Default: 1 +level = DEBUG ; Default: WARNING +handlers = app ; Default: stderr +propagate = 0 ; Default: 1 [logger_SimulateCommand] qualname = SimulateCommand @@ -43,7 +43,7 @@ propagate = 0 ; Default: 1 [logger_SumCommand] qualname = SumCommand level = NOTSET ; Default: WARNING -handlers = app ; Default: stderr +handlers = splunklib ; Default: stderr propagate = 0 ; Default: 1 [handlers] From f1effb2c05e9bc6fadc0128e57f87df3fbbdd6b8 Mon Sep 17 00:00:00 2001 From: PKing70 <39703314+PKing70@users.noreply.github.com> Date: Wed, 8 May 2019 14:13:24 -0700 Subject: [PATCH 32/37] Revise README (#253) Add Python and Splunk versions tested for compatibility https://jira.splunk.com/browse/DVPL-7485 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e23767b76..483ad5007 100644 --- a/README.md +++ b/README.md @@ -38,14 +38,14 @@ Here's what you need to get going with the Splunk SDK for Python. #### Python -The Splunk SDK for Python requires Python 2.7+, including Python 3. +The Splunk SDK for Python requires Python 2.7+, including Python 3. The Splunk SDK for Python has been tested with Python v2.7 and v3.5. #### Splunk If you haven't already installed Splunk, download it [here](http://www.splunk.com/download). For more about installing and running Splunk and system requirements, see -[Installing & Running Splunk](http://dev.splunk.com/view/SP-CAAADRV). +[Installing & Running Splunk](http://dev.splunk.com/view/SP-CAAADRV). The Splunk SDK for Python has been tested with Splunk Enterprise 7.0 and 7.2. #### Splunk SDK for Python Get the Splunk SDK for Python; [download the SDK as a ZIP](http://dev.splunk.com/view/SP-CAAAEBB) From 84dab82ee8428e4a5b1dfc26707aa92144638d0e Mon Sep 17 00:00:00 2001 From: Shakeel Mohamed Date: Tue, 14 May 2019 11:55:27 -0700 Subject: [PATCH 33/37] update CI password --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d8d5f77b3..428b02d95 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ before_install: # Create .splunkrc file with default credentials - echo host=127.0.0.1 >> $HOME/.splunkrc - echo username=admin >> $HOME/.splunkrc - - echo password=changeme >> $HOME/.splunkrc + - echo password=changed! >> $HOME/.splunkrc # Set SPLUNK_HOME - export SPLUNK_HOME="/opt/splunk" # Pull docker image From 79ad17a8ec2576bbd7de70475cbf7b547ffca5b7 Mon Sep 17 00:00:00 2001 From: bj8798 Date: Thu, 1 Aug 2019 22:40:19 +0530 Subject: [PATCH 34/37] convert csv.Dialect to string for python3 (#259) --- splunklib/searchcommands/validators.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/splunklib/searchcommands/validators.py b/splunklib/searchcommands/validators.py index dca4cf422..f3e2e5213 100644 --- a/splunklib/searchcommands/validators.py +++ b/splunklib/searchcommands/validators.py @@ -249,10 +249,10 @@ class List(Validator): class Dialect(csv.Dialect): """ Describes the properties of list option values. """ strict = True - delimiter = b',' - quotechar = b'"' + delimiter = str(',') + quotechar = str('"') doublequote = True - lineterminator = b'\n' + lineterminator = str('\n') skipinitialspace = True quoting = csv.QUOTE_MINIMAL From 66495f9bfdcdef7ee42bdcff5c023533a1b93fe7 Mon Sep 17 00:00:00 2001 From: Moshe Kaplan Date: Tue, 6 Aug 2019 13:49:03 -0400 Subject: [PATCH 35/37] Correct return type in documentation --- splunklib/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/splunklib/client.py b/splunklib/client.py index 1e624ba42..d2631edba 100644 --- a/splunklib/client.py +++ b/splunklib/client.py @@ -3569,7 +3569,7 @@ class KVStoreCollection(Entity): def data(self): """Returns data object for this Collection. - :rtype: :class:`KVStoreData` + :rtype: :class:`KVStoreCollectionData` """ return KVStoreCollectionData(self) From 64caeff213e8aaa8f8cfd34dc60390d073152b51 Mon Sep 17 00:00:00 2001 From: Liying Jiang Date: Wed, 11 Sep 2019 10:51:11 -0700 Subject: [PATCH 36/37] update version to 1.6.7 --- examples/searchcommands_app/setup.py | 2 +- splunklib/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/searchcommands_app/setup.py b/examples/searchcommands_app/setup.py index 7fb650d6a..b504dc607 100755 --- a/examples/searchcommands_app/setup.py +++ b/examples/searchcommands_app/setup.py @@ -439,7 +439,7 @@ def run(self): setup( description='Custom Search Command examples', name=os.path.basename(project_dir), - version='1.6.6', + version='1.6.7', author='Splunk, Inc.', author_email='devinfo@splunk.com', url='http://github.com/splunk/splunk-sdk-python', diff --git a/splunklib/__init__.py b/splunklib/__init__.py index 59daf9ea8..e0f7251df 100644 --- a/splunklib/__init__.py +++ b/splunklib/__init__.py @@ -16,5 +16,5 @@ from __future__ import absolute_import from splunklib.six.moves import map -__version_info__ = (1, 6, 6) +__version_info__ = (1, 6, 7) __version__ = ".".join(map(str, __version_info__)) From f9732321985a7941f672dc8929f2b267f0b0b443 Mon Sep 17 00:00:00 2001 From: Liying Jiang Date: Thu, 12 Sep 2019 10:21:40 -0700 Subject: [PATCH 37/37] update changelog --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dd5a5589..bca13bc2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Splunk SDK for Python Changelog +## Version 1.6.7 + +### Changes + +* Updated the Splunk SDK for Python to work with the Python 3 version of Splunk Enterprise on Windows +* Improved the performance of deleting/updating an input +* Added logging to custom search commands app to showcase how to do logging in custom search commands by using the Splunk SDK for Python + ## Version 1.6.6 ### Bug fixes