Skip to content

Commit

Permalink
Release/1.6.6 (#234)
Browse files Browse the repository at this point in the history
* Add return code for invalid args block

Return non-zero rc instead of None

* Added headers argument to get requests so that additional headers can be passed in similar to post reqeusts

* Added the option of always adding a particular header to a request. Also fixed unicode xml decoding error

* Adding additional comments to denote that a headers object is possible to pass in as a kwarg

* auto-doc all class constructor parameters

* 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.

* update assertion

* Make the explorer example compatible w/ Python 3

... by using the six library

* Update binding.py

*  Run CI against Splunk 7.2

* tests: relax restart timeout

* 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

* Preparing Release 1.6.6

* Cleaned up CHANGELOG.md

* Update version to 1.6.6 in User-Agent header

* Rev version of examples/searchcommands_app/setup.py to 1.6.6
  • Loading branch information
dan1 authored Dec 5, 2018
1 parent 9e82ab7 commit e35783d
Show file tree
Hide file tree
Showing 17 changed files with 111 additions and 70 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
*.observed
venv/
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Splunk SDK for Python Changelog

## Versiom 1.6.6

### Bug fixes

* Fix ssl verify to require certs when true

### Minor changes

* Make the explorer example compatible w/ Python 3
* Add full support for unicode in SearchCommands
* Add return code for invalid_args block

## Version 1.6.5

### Bug fixes
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[![Build Status](https://travis-ci.org/splunk/splunk-sdk-python.svg?branch=master)](https://travis-ci.org/splunk/splunk-sdk-python)
# The Splunk Software Development Kit for Python

#### Version 1.6.5
#### Version 1.6.6

The Splunk Software Development Kit (SDK) for Python contains library code and
examples designed to enable developers to build applications using Splunk.
Expand Down
2 changes: 2 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,3 +248,5 @@

# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'

autoclass_content = 'both'
5 changes: 3 additions & 2 deletions examples/explorer/explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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))
Expand Down
30 changes: 16 additions & 14 deletions examples/explorer/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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))

Expand All @@ -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)
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion examples/searchcommands_app/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ def run(self):
setup(
description='Custom Search Command examples',
name=os.path.basename(project_dir),
version='1.6.5',
version='1.6.6',
author='Splunk, Inc.',
author_email='devinfo@splunk.com',
url='http://github.com/splunk/splunk-sdk-python',
Expand Down
2 changes: 1 addition & 1 deletion splunklib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@

from __future__ import absolute_import
from splunklib.six.moves import map
__version_info__ = (1, 6, 5)
__version_info__ = (1, 6, 6)
__version__ = ".".join(map(str, __version_info__))
60 changes: 38 additions & 22 deletions splunklib/binding.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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.
Expand All @@ -465,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
Expand All @@ -478,6 +481,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]:
Expand Down Expand Up @@ -613,7 +617,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.
Expand All @@ -636,6 +640,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
Expand Down Expand Up @@ -663,10 +669,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.additional_headers + self._auth_headers
response = self.http.get(path, all_headers, **query)
return response

@_authentication
Expand Down Expand Up @@ -738,7 +748,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

Expand Down Expand Up @@ -804,7 +814,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,
Expand Down Expand Up @@ -858,6 +868,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()
Expand Down Expand Up @@ -968,6 +979,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``
Expand Down Expand Up @@ -1108,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):
Expand Down Expand Up @@ -1190,7 +1206,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"))
Expand Down Expand Up @@ -1280,8 +1296,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):
Expand Down Expand Up @@ -1317,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.
Expand All @@ -1341,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)
Expand All @@ -1352,7 +1368,7 @@ def request(url, message, **kwargs):
head = {
"Content-Length": str(len(body)),
"Host": host,
"User-Agent": "splunk-sdk-python/1.6.5",
"User-Agent": "splunk-sdk-python/1.6.6",
"Accept": "*/*",
"Connection": "Close",
} # defaults
Expand Down
Loading

0 comments on commit e35783d

Please sign in to comment.