Skip to content

Commit 048e0c9

Browse files
author
Sam Stelle
committed
Merge branch 'inputFix' into develop
2 parents 49f57a7 + 1d7df1a commit 048e0c9

File tree

3 files changed

+89
-8
lines changed

3 files changed

+89
-8
lines changed

splunklib/binding.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,14 @@ class UrlEncoded(str):
115115
UrlEncoded('ab c') + 'de f' == UrlEncoded('ab cde f')
116116
'ab c' + UrlEncoded('de f') == UrlEncoded('ab cde f')
117117
"""
118-
def __new__(self, val='', skip_encode=False):
118+
def __new__(self, val='', skip_encode=False, encode_slash=False):
119119
if isinstance(val, UrlEncoded):
120120
# Don't urllib.quote something already URL encoded.
121121
return val
122122
elif skip_encode:
123123
return str.__new__(self, val)
124+
elif encode_slash:
125+
return str.__new__(self, urllib.quote_plus(val))
124126
else:
125127
# When subclassing str, just call str's __new__ method
126128
# with your class and the value you want to have in the

splunklib/client.py

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1154,10 +1154,11 @@ def __getitem__(self, key):
11541154
# x[a,b] is translated to x.__getitem__( (a,b) ), so we
11551155
# have to extract values out.
11561156
key, ns = key
1157+
key = UrlEncoded(key, encode_slash=True)
11571158
response = self.get(key, owner=ns.owner, app=ns.app)
11581159
else:
1160+
key = UrlEncoded(key, encode_slash=True)
11591161
response = self.get(key)
1160-
11611162
entries = self._load_list(response)
11621163
if len(entries) > 1:
11631164
raise AmbiguousReferenceException("Found multiple entities named '%s'; please specify a namespace." % key)
@@ -1191,6 +1192,7 @@ def __iter__(self, **kwargs):
11911192
for entity in saved_searches:
11921193
print "Saved search named %s" % entity.name
11931194
"""
1195+
11941196
for item in self.iter(**kwargs):
11951197
yield item
11961198

@@ -1397,6 +1399,8 @@ def list(self, count=None, **kwargs):
13971399
return list(self.iter(count=count, **kwargs))
13981400

13991401

1402+
1403+
14001404
class Collection(ReadOnlyCollection):
14011405
"""A collection of entities.
14021406
@@ -1442,6 +1446,7 @@ class Collection(ReadOnlyCollection):
14421446
:class:`Collection` does no caching. Each call makes at least one
14431447
round trip to the server to fetch data.
14441448
"""
1449+
14451450
def create(self, name, **params):
14461451
"""Creates a new entity in this collection.
14471452
@@ -1516,6 +1521,7 @@ def delete(self, name, **params):
15161521
saved_searches.delete('my_saved_search')
15171522
assert 'my_saved_search' not in saved_searches
15181523
"""
1524+
name = UrlEncoded(name, encode_slash=True)
15191525
if 'namespace' in params:
15201526
namespace = params.pop('namespace')
15211527
params['owner'] = namespace.owner
@@ -1533,6 +1539,56 @@ def delete(self, name, **params):
15331539
raise
15341540
return self
15351541

1542+
def get(self, name="", owner=None, app=None, sharing=None, **query):
1543+
"""Performs a GET request to the server on the collection.
1544+
1545+
If *owner*, *app*, and *sharing* are omitted, this method takes a
1546+
default namespace from the :class:`Service` object for this :class:`Endpoint`.
1547+
All other keyword arguments are included in the URL as query parameters.
1548+
1549+
:raises AuthenticationError: Raised when the ``Service`` is not logged in.
1550+
:raises HTTPError: Raised when an error in the request occurs.
1551+
:param path_segment: A path segment relative to this endpoint.
1552+
:type path_segment: ``string``
1553+
:param owner: The owner context of the namespace (optional).
1554+
:type owner: ``string``
1555+
:param app: The app context of the namespace (optional).
1556+
:type app: ``string``
1557+
:param sharing: The sharing mode for the namespace (optional).
1558+
:type sharing: "global", "system", "app", or "user"
1559+
:param query: All other keyword arguments, which are used as query
1560+
parameters.
1561+
:type query: ``string``
1562+
:return: The response from the server.
1563+
:rtype: ``dict`` with keys ``body``, ``headers``, ``reason``,
1564+
and ``status``
1565+
1566+
Example:
1567+
1568+
import splunklib.client
1569+
s = client.service(...)
1570+
saved_searches = s.saved_searches
1571+
saved_searches.get("my/saved/search") == \\
1572+
{'body': ...a response reader object...,
1573+
'headers': [('content-length', '26208'),
1574+
('expires', 'Fri, 30 Oct 1998 00:00:00 GMT'),
1575+
('server', 'Splunkd'),
1576+
('connection', 'close'),
1577+
('cache-control', 'no-store, max-age=0, must-revalidate, no-cache'),
1578+
('date', 'Fri, 11 May 2012 16:30:35 GMT'),
1579+
('content-type', 'text/xml; charset=utf-8')],
1580+
'reason': 'OK',
1581+
'status': 200}
1582+
saved_searches.get('nonexistant/search') # raises HTTPError
1583+
s.logout()
1584+
saved_searches.get() # raises AuthenticationError
1585+
1586+
"""
1587+
name = UrlEncoded(name, encode_slash=True)
1588+
return super(Collection, self).get(name, owner, app, sharing, **query)
1589+
1590+
1591+
15361592

15371593
class ConfigurationFile(Collection):
15381594
"""This class contains all of the stanzas from one configuration file.
@@ -1953,9 +2009,13 @@ def __init__(self, service, kindmap=None):
19532009
Collection.__init__(self, service, PATH_INPUTS, item=Input)
19542010

19552011
def __getitem__(self, key):
2012+
# The key needed to retrieve the input needs it's parenthesis to be URL encoded
2013+
# based on the REST API for input
2014+
# <http://docs.splunk.com/Documentation/Splunk/latest/RESTAPI/RESTinput>
19562015
if isinstance(key, tuple) and len(key) == 2:
19572016
# Fetch a single kind
19582017
key, kind = key
2018+
key = UrlEncoded(key, encode_slash=True)
19592019
try:
19602020
response = self.get(self.kindpath(kind) + "/" + key)
19612021
entries = self._load_list(response)
@@ -1974,6 +2034,7 @@ def __getitem__(self, key):
19742034
# Iterate over all the kinds looking for matches.
19752035
kind = None
19762036
candidate = None
2037+
key = UrlEncoded(key, encode_slash=True)
19772038
for kind in self.kinds:
19782039
try:
19792040
response = self.get(kind + "/" + key)
@@ -1992,7 +2053,7 @@ def __getitem__(self, key):
19922053
else:
19932054
raise
19942055
if candidate is None:
1995-
raise KeyError(key) # Never found a match
2056+
raise KeyError(key) # Never found a match.
19962057
else:
19972058
return candidate
19982059

@@ -2065,11 +2126,14 @@ def create(self, name, kind, **kwargs):
20652126
# If we created an input with restrictToHost set, then
20662127
# its path will be <restrictToHost>:<name>, not just <name>,
20672128
# and we have to adjust accordingly.
2129+
2130+
# Url encodes the name of the entity.
2131+
name = UrlEncoded(name, encode_slash=True)
20682132
path = _path(
20692133
self.path + kindpath,
20702134
'%s:%s' % (kwargs['restrictToHost'], name) \
20712135
if kwargs.has_key('restrictToHost') else name
2072-
)
2136+
)
20732137
return Input(self.service, path, kind)
20742138

20752139
def delete(self, name, kind=None):
@@ -2146,7 +2210,7 @@ def itemmeta(self, kind):
21462210
def _get_kind_list(self, subpath=None):
21472211
if subpath is None:
21482212
subpath = []
2149-
2213+
21502214
kinds = []
21512215
response = self.get('/'.join(subpath))
21522216
content = _load_atom_entries(response)
@@ -2204,12 +2268,12 @@ def kindpath(self, kind):
22042268
:rtype: ``string``
22052269
"""
22062270
if kind in self.kinds:
2207-
return kind
2271+
return UrlEncoded(kind, skip_encode=True)
22082272
# Special cases
22092273
elif kind == 'tcp':
2210-
return 'tcp/raw'
2274+
return UrlEncoded('tcp/raw', skip_encode=True)
22112275
elif kind == 'splunktcp':
2212-
return 'tcp/cooked'
2276+
return UrlEncoded('tcp/cooked', skip_encode=True)
22132277
else:
22142278
raise ValueError("No such kind on server: %s" % kind)
22152279

@@ -2276,6 +2340,7 @@ def list(self, *kinds, **kwargs):
22762340
path = self.kindpath(kind)
22772341
logging.debug("Path for inputs: %s", path)
22782342
try:
2343+
path = UrlEncoded(path, skip_encode=True)
22792344
response = self.get(path, **kwargs)
22802345
except HTTPError, he:
22812346
if he.status == 404: # No inputs of this kind
@@ -2300,6 +2365,7 @@ def list(self, *kinds, **kwargs):
23002365
for kind in kinds:
23012366
response = None
23022367
try:
2368+
kind = UrlEncoded(kind, skip_encode=True)
23032369
response = self.get(self.kindpath(kind), search=search)
23042370
except HTTPError as e:
23052371
if e.status == 404:

tests/test_collection.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,19 @@ def test_getitem_with_namespace_sample_in_changelog(self):
244244
ns = client.namespace(owner='nobody', app='search')
245245
result = self.service.saved_searches['Top five sourcetypes', ns]
246246

247+
def test_collection_search_get(self):
248+
for search in self.service.saved_searches:
249+
self.assertEqual(self.service.saved_searches[search.name].path, search.path)
250+
self.assertEqual(200, self.service.saved_searches.get(search.name).status)
251+
252+
def test_collection_inputs_getitem(self):
253+
valid_kinds = self.service.inputs._get_kind_list()
254+
valid_kinds.remove("script")
255+
for inp in self.service.inputs.list(*valid_kinds):
256+
self.assertTrue(self.service.inputs[inp.name])
257+
258+
259+
247260
if __name__ == "__main__":
248261
try:
249262
import unittest2 as unittest

0 commit comments

Comments
 (0)