Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ryancurrah committed Jan 3, 2017
0 parents commit 2846fc2
Show file tree
Hide file tree
Showing 16 changed files with 809 additions and 0 deletions.
1 change: 1 addition & 0 deletions .coveralls.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
service_name: travis-ci
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
*.pyc
.idea
.tox
.cache
.coverage
*.egg-info
.DS_Store
17 changes: 17 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
language: python
python:
- "2.7"
- "3.3"
- "3.4"
- "3.5"
# command to install dependencies
before_install:
- pip install -r test-requirements.txt
install:
- pip install -r requirements.txt
# command to run tests
script:
- export PYTHONPATH='./'
- py.test -v --pep8 searchsplunk --cov searchsplunk --cov-report term-missing
after_success:
- coveralls
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## [Unreleased]
## 0.1.0 (January, 3, 2017)

* Initial release
340 changes: 340 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
[![Build Status](https://travis-ci.org/ryancurrah/searchsplunk.svg?branch=master)](https://travis-ci.org/ryancurrah/searchsplunk)

[![Coverage Status](https://coveralls.io/repos/github/ryancurrah/searchsplunk/badge.svg?branch=master)](https://coveralls.io/github/ryancurrah/searchsplunk?branch=master)


# Search Splunk
Easily create Splunk searches from Python and get the result as a Python object

# Requires
- requests: https://pypi.python.org/pypi/requests

# Usage instructions

```python
import pprint
pp = pprint.PrettyPrinter(indent=2)

from searchsplunk import SearchSplunk
s = SearchSplunk('https://splunk.acme.com:8089', 'MYUSER', 'MYPASS', ssl_verify=True)
result = s.search('sourcetype=salt:grains openstack_uid=e0303456c-d5a3-789f-ab68-8f27561ffa0f | dedup openstack_uid')

pp.pprint(result)
{
u'fields': [ { u'name': u'_bkt'},
{ u'name': u'_cd'},
{ u'name': u'_indextime'},
{ u'name': u'_kv'},
{ u'name': u'_raw'},
{ u'name': u'_serial'},
{ u'name': u'_si'},
{ u'name': u'_sourcetype'},
{ u'name': u'_subsecond'},
{ u'name': u'_time'},
{ u'name': u'host'},
{ u'name': u'index'},
{ u'name': u'linecount'},
{ u'name': u'openstack_uid'},
{ u'name': u'source'},
{ u'name': u'sourcetype'},
{ u'name': u'splunk_server'}],
u'init_offset': 0,
u'messages': [],
u'preview': False,
u'results': [ { u'_bkt': u'main~1122~25B521A6-9612-407D-A1BA-F8KJSEBB7628',
u'_cd': u'1122:290410720',
u'_indextime': u'1435071966',
u'_kv': u'1',
u'_raw': u"somefile contents",
u'_serial': u'0',
u'_si': [u'splunkserv', u'main'],
u'_sourcetype': u'salt:grains',
u'_time': u'2015-06-23T11:06:05.000-04:00',
u'host': u'server-7654.acme.com',
u'index': u'main',
u'linecount': u'17',
u'openstack_uid': u'e0303456c-d5a3-789f-ab68-8f27561ffa0f',
u'source': u'/etc/salt/grains',
u'sourcetype': u'salt:grains',
u'splunk_server': u'splunkmaster'}]
}
```

## Author
[Ryan Currah](ryan@currah.ca)

## License
GPL v2

1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
requests>=2.7.0
13 changes: 13 additions & 0 deletions run_tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
# Runs the pytest tests
set -e

# Export as env variables
export PYTHONPATH='./'

# Install test requirements
pip install -r requirements.txt
pip install -r test-requirements.txt

# Run tests
py.test -v
Empty file added searchsplunk/__init__.py
Empty file.
11 changes: 11 additions & 0 deletions searchsplunk/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""
Splunk exception clasess
"""


class SplunkError(Exception):
pass


class SplunkInvalidCredentials(SplunkError):
pass
170 changes: 170 additions & 0 deletions searchsplunk/searchsplunk.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import re
import warnings
import requests
from xml.dom import minidom
from .exceptions import SplunkInvalidCredentials
from .version import __version__

warnings.filterwarnings('ignore')


class Splunk(object):
"""
Splunk base class
"""
session_key = None

def __init__(self, url, username, password, ssl_verify=True):
"""
Initialize Splunk search client
:param url: Url of splunk server eg: https://splunk.acme.com:8089
:param username: Splunk username
:param password: Splunk password
:param ssl_verify: True or False whether or not to verify Splunk
SSL certificate
"""
self.url = url.rstrip('/')
self.username = username
self.password = password
self.ssl_verify = ssl_verify
self.__login()
return

@staticmethod
def version():
"""
Get the module version
:return: The module version
"""
return __version__

def __login(self):
"""
Gets and sets a session key, sessions by default in Splunk are
valid for one hour
"""
method = 'POST'
uri = '/services/auth/login'

r = self.request(
method,
uri,
body={'username': self.username, 'password': self.password}
)

try:
self.session_key = minidom.parseString(
r.text
).getElementsByTagName('sessionKey')[0].childNodes[0].nodeValue
except (IndexError, KeyError):
raise SplunkInvalidCredentials(
'HTTP status code:\n{0}\nError message:\n{1]'.format(
r.status_code,
r.text
)
)
return

@property
def session_header(self):
"""
Return dictionary auth session header if logged in
"""
return {
'Authorization': 'Splunk {0}'.format(self.session_key)
} if self.session_key else {}

def request(self, method, uri, body={}, params={}, headers={}, auth=()):
"""
Make HTTP requests
:param method: post, get, put, delete
:param uri: /some/api/uri/url/will/be/appended/automatically
:param body: body data of dict
:param params: get parameters of dict
:param headers: http headers of dict
:param auth: auth tuple username, password of tuple
:return: A Requests response object
"""
headers.update(self.session_header)

return requests.request(
method,
'{0}{1}'.format(self.url, uri),
headers=headers,
data=body,
params=params,
auth=auth,
verify=self.ssl_verify
)


class SearchSplunk(Splunk):
"""
Splunk search class
"""
def search(self, search_query):
"""
Creates search jobs in Splunk and returns the result or raises
exception
:param search_query: The search query string
:return: Search result python object
"""
sid = self.__start_search(search_query)

search_done = False
while not search_done:
if self.__search_status(sid):
search_done = True
return self.__search_result(sid)

def __start_search(self, search_query):
"""
Starts a search job in Splunk
:param search_query: The search query string
:return: The Splunk search job id otherwise raises exceptions
"""
method = 'POST'
uri = '/services/search/jobs'

if not search_query.startswith('search'):
search_query = '{0} {1}'.format('search ', search_query)

s = self.request(method, uri, body={'search': search_query})

return minidom.parseString(
s.text
).getElementsByTagName('sid')[0].childNodes[0].nodeValue

def __search_status(self, sid):
"""
Gets the status of a search job
:param sid: The Splunk search job id
:return: True for search job done or False for search job not done
"""
method = 'GET'
uri = '/services/search/jobs/{0}'

s = self.request(method, uri.format(sid))
return int(re.compile('isDone">(0|1)').search(s.text).groups()[0])

def __search_result(self, sid):
"""
Returns the results of a search job
:param sid: The Splunk search job id
:return: The search job as a python object
"""
method = 'GET'
uri = '/services/search/jobs/{0}/results'

return self.request(
method,
uri.format(sid),
params={'output_mode': 'json'}
).json()
Loading

0 comments on commit 2846fc2

Please sign in to comment.