Skip to content

Commit

Permalink
Merge pull request #595 from splunk/release/2.1.0
Browse files Browse the repository at this point in the history
Release/2.1.0
  • Loading branch information
maszyk99 authored Oct 30, 2024
2 parents c6f7d32 + d36db5e commit 1a3e290
Show file tree
Hide file tree
Showing 13 changed files with 374 additions and 127 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Python CI

on:
[ push, pull_request ]
[ push, pull_request, workflow_dispatch ]

jobs:
build:
Expand All @@ -11,7 +11,7 @@ jobs:
matrix:
os:
- ubuntu-latest
python: [ 3.7, 3.9]
python: [ 3.7, 3.9, 3.13]
splunk-version:
- "8.1"
- "8.2"
Expand All @@ -22,8 +22,8 @@ jobs:
- name: Checkout code
uses: actions/checkout@v3

- name: Run docker-compose
run: SPLUNK_VERSION=${{matrix.splunk-version}} docker-compose up -d
- name: Run docker compose
run: SPLUNK_VERSION=${{matrix.splunk-version}} docker compose up -d

- name: Setup Python
uses: actions/setup-python@v4
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Splunk Enterprise SDK for Python Changelog

## Version 2.1.0

### Changes
* [#516](https://github.com/splunk/splunk-sdk-python/pull/516) Added support for macros
* Remove deprecated `wrap_socket` in `Contex` class.
* Added explicit support for self signed certificates in https
* Enforce minimal required tls version in https connection
* Add support for python 3.13
* [#559](https://github.com/splunk/splunk-sdk-python/pull/559/) Add exception logging

## Version 2.0.2

### Minor changes
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

# The Splunk Enterprise Software Development Kit for Python

#### Version 2.0.2
#### Version 2.1.0

The Splunk Enterprise Software Development Kit (SDK) for Python contains library code designed to enable developers to build applications using the Splunk platform.

Expand All @@ -25,9 +25,9 @@ The Splunk Enterprise SDK for Python contains library code, and its examples are

Here's what you need to get going with the Splunk Enterprise SDK for Python.

* Python 3.7 or Python 3.9
* Python 3.7, Python 3.9 and Python 3.13

The Splunk Enterprise SDK for Python is compatible with python3 and has been tested with Python v3.7 and v3.9.
The Splunk Enterprise SDK for Python is compatible with python3 and has been tested with Python v3.7, v3.9 and v3.13.

* Splunk Enterprise 9.2 or 8.2

Expand Down
111 changes: 1 addition & 110 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,124 +14,15 @@
# License for the specific language governing permissions and limitations
# under the License.

from setuptools import setup, Command

import os
import sys
from setuptools import setup

import splunklib

failed = False

def run_test_suite():
import unittest

def mark_failed():
global failed
failed = True

class _TrackingTextTestResult(unittest._TextTestResult):
def addError(self, test, err):
unittest._TextTestResult.addError(self, test, err)
mark_failed()

def addFailure(self, test, err):
unittest._TextTestResult.addFailure(self, test, err)
mark_failed()

class TrackingTextTestRunner(unittest.TextTestRunner):
def _makeResult(self):
return _TrackingTextTestResult(
self.stream, self.descriptions, self.verbosity)

original_cwd = os.path.abspath(os.getcwd())
os.chdir('tests')
suite = unittest.defaultTestLoader.discover('.')
runner = TrackingTextTestRunner(verbosity=2)
runner.run(suite)
os.chdir(original_cwd)

return failed


def run_test_suite_with_junit_output():
try:
import unittest2 as unittest
except ImportError:
import unittest
import xmlrunner
original_cwd = os.path.abspath(os.getcwd())
os.chdir('tests')
suite = unittest.defaultTestLoader.discover('.')
xmlrunner.XMLTestRunner(output='../test-reports').run(suite)
os.chdir(original_cwd)


class CoverageCommand(Command):
"""setup.py command to run code coverage of the test suite."""
description = "Create an HTML coverage report from running the full test suite."
user_options = []

def initialize_options(self):
pass

def finalize_options(self):
pass

def run(self):
try:
import coverage
except ImportError:
print("Could not import coverage. Please install it and try again.")
exit(1)
cov = coverage.coverage(source=['splunklib'])
cov.start()
run_test_suite()
cov.stop()
cov.html_report(directory='coverage_report')


class TestCommand(Command):
"""setup.py command to run the whole test suite."""
description = "Run test full test suite."
user_options = []

def initialize_options(self):
pass

def finalize_options(self):
pass

def run(self):
failed = run_test_suite()
if failed:
sys.exit(1)


class JunitXmlTestCommand(Command):
"""setup.py command to run the whole test suite."""
description = "Run test full test suite with JUnit-formatted output."
user_options = []

def initialize_options(self):
pass

def finalize_options(self):
pass

def run(self):
run_test_suite_with_junit_output()


setup(
author="Splunk, Inc.",

author_email="devinfo@splunk.com",

cmdclass={'coverage': CoverageCommand,
'test': TestCommand,
'testjunit': JunitXmlTestCommand},

description="The Splunk Software Development Kit for Python.",

license="http://www.apache.org/licenses/LICENSE-2.0",
Expand Down
2 changes: 1 addition & 1 deletion splunklib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,5 @@ def setup_logging(level, log_format=DEFAULT_LOG_FORMAT, date_format=DEFAULT_DATE
datefmt=date_format)


__version_info__ = (2, 0, 2)
__version_info__ = (2, 1, 0)
__version__ = ".".join(map(str, __version_info__))
9 changes: 8 additions & 1 deletion splunklib/binding.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,8 @@ class Context:
:type scheme: "https" or "http"
:param verify: Enable (True) or disable (False) SSL verification for https connections.
:type verify: ``Boolean``
:param self_signed_certificate: Specifies if self signed certificate is used
:type self_signed_certificate: ``Boolean``
:param sharing: The sharing mode for the namespace (the default is "user").
:type sharing: "global", "system", "app", or "user"
:param owner: The owner context of the namespace (optional, the default is "None").
Expand Down Expand Up @@ -526,6 +528,7 @@ def __init__(self, handler=None, **kwargs):
self.bearerToken = kwargs.get("splunkToken", "")
self.autologin = kwargs.get("autologin", False)
self.additional_headers = kwargs.get("headers", [])
self._self_signed_certificate = kwargs.get("self_signed_certificate", True)

# Store any cookies in the self.http._cookies dict
if "cookie" in kwargs and kwargs['cookie'] not in [None, _NoAuthenticationToken]:
Expand Down Expand Up @@ -604,7 +607,11 @@ def connect(self):
"""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
if self.scheme == "https":
sock = ssl.wrap_socket(sock)
context = ssl.create_default_context()
context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
context.check_hostname = not self._self_signed_certificate
context.verify_mode = ssl.CERT_NONE if self._self_signed_certificate else ssl.CERT_REQUIRED
sock = context.wrap_socket(sock, server_hostname=self.host)
sock.connect((socket.gethostbyname(self.host), self.port))
return sock

Expand Down
96 changes: 95 additions & 1 deletion splunklib/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
PATH_JOBS = "search/jobs/"
PATH_JOBS_V2 = "search/v2/jobs/"
PATH_LOGGER = "/services/server/logger/"
PATH_MACROS = "configs/conf-macros/"
PATH_MESSAGES = "messages/"
PATH_MODULAR_INPUTS = "data/modular-inputs"
PATH_ROLES = "authorization/roles/"
Expand Down Expand Up @@ -667,6 +668,15 @@ def saved_searches(self):
"""
return SavedSearches(self)

@property
def macros(self):
"""Returns the collection of macros.
:return: A :class:`Macros` collection of :class:`Macro`
entities.
"""
return Macros(self)

@property
def settings(self):
"""Returns the configuration settings for this instance of Splunk.
Expand Down Expand Up @@ -3440,6 +3450,90 @@ def create(self, name, search, **kwargs):
return Collection.create(self, name, search=search, **kwargs)


class Macro(Entity):
"""This class represents a search macro."""
def __init__(self, service, path, **kwargs):
Entity.__init__(self, service, path, **kwargs)

@property
def args(self):
"""Returns the macro arguments.
:return: The macro arguments.
:rtype: ``string``
"""
return self._state.content.get('args', '')

@property
def definition(self):
"""Returns the macro definition.
:return: The macro definition.
:rtype: ``string``
"""
return self._state.content.get('definition', '')

@property
def errormsg(self):
"""Returns the validation error message for the macro.
:return: The validation error message for the macro.
:rtype: ``string``
"""
return self._state.content.get('errormsg', '')

@property
def iseval(self):
"""Returns the eval-based definition status of the macro.
:return: The iseval value for the macro.
:rtype: ``string``
"""
return self._state.content.get('iseval', '0')

def update(self, definition=None, **kwargs):
"""Updates the server with any changes you've made to the current macro
along with any additional arguments you specify.
:param `definition`: The macro definition (optional).
:type definition: ``string``
:param `kwargs`: Additional arguments (optional). Available parameters are:
'disabled', 'iseval', 'validation', and 'errormsg'.
:type kwargs: ``dict``
:return: The :class:`Macro`.
"""
# Updates to a macro *require* that the definition be
# passed, so we pass the current definition if a value wasn't
# provided by the caller.
if definition is None: definition = self.content.definition
Entity.update(self, definition=definition, **kwargs)
return self

@property
def validation(self):
"""Returns the validation expression for the macro.
:return: The validation expression for the macro.
:rtype: ``string``
"""
return self._state.content.get('validation', '')


class Macros(Collection):
"""This class represents a collection of macros. Retrieve this
collection using :meth:`Service.macros`."""
def __init__(self, service):
Collection.__init__(
self, service, PATH_MACROS, item=Macro)

def create(self, name, definition, **kwargs):
""" Creates a macro.
:param name: The name for the macro.
:type name: ``string``
:param definition: The macro definition.
:type definition: ``string``
:param kwargs: Additional arguments (optional). Available parameters are:
'disabled', 'iseval', 'validation', and 'errormsg'.
:type kwargs: ``dict``
:return: The :class:`Macros` collection.
"""
return Collection.create(self, name, definition=definition, **kwargs)


class Settings(Entity):
"""This class represents configuration settings for a Splunk service.
Retrieve this collection using :meth:`Service.settings`."""
Expand Down Expand Up @@ -3905,4 +3999,4 @@ def batch_save(self, *documents):
data = json.dumps(documents)

return json.loads(
self._post('batch_save', headers=KVStoreCollectionData.JSON_HEADER, body=data).body.read().decode('utf-8'))
self._post('batch_save', headers=KVStoreCollectionData.JSON_HEADER, body=data).body.read().decode('utf-8'))
20 changes: 20 additions & 0 deletions splunklib/modularinput/event_writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# under the License.

import sys
import traceback

from splunklib.utils import ensure_str
from .event import ET
Expand Down Expand Up @@ -66,6 +67,25 @@ def log(self, severity, message):
self._err.write(f"{severity} {message}\n")
self._err.flush()

def log_exception(self, message, exception=None, severity=None):
"""Logs messages about the exception thrown by this modular input to Splunk.
These messages will show up in Splunk's internal logs.
:param message: ``string``, message to log.
:param exception: ``Exception``, exception thrown by this modular input; if none, sys.exc_info() is used
:param severity: ``string``, severity of message, see severities defined as class constants. Default severity: ERROR
"""
if exception is not None:
tb_str = traceback.format_exception(type(exception), exception, exception.__traceback__)
else:
tb_str = traceback.format_exc()

if severity is None:
severity = EventWriter.ERROR

self._err.write(("%s %s - %s" % (severity, message, tb_str)).replace("\n", " "))
self._err.flush()

def write_xml_document(self, document):
"""Writes a string representation of an
``ElementTree`` object to the output stream.
Expand Down
Loading

0 comments on commit 1a3e290

Please sign in to comment.