Skip to content

Commit 70a6a2b

Browse files
author
Shakeel Mohamed
committed
Merge branch 'develop' into feature/storage_passwords
2 parents 1df5cb4 + 5c03708 commit 70a6a2b

File tree

12 files changed

+159
-48
lines changed

12 files changed

+159
-48
lines changed

examples/searchcommands_app/bin/generatehello.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@
1616

1717
import sys, time
1818
from splunklib.searchcommands import \
19-
dispatch, GeneratingCommand, Configuration, Option, validators
19+
dispatch, GeneratingCommand, Configuration, Option, validators
2020

2121
@Configuration()
2222
class GenerateHelloCommand(GeneratingCommand):
23-
count = Option(require=True, validate=validators.Integer())
23+
count = Option(require=True, validate=validators.Integer())
2424

25-
def generate(self):
26-
for i in range(1, self.count + 1):
27-
text = 'Hello World %d' % i
28-
yield {'_time': time.time(), 'event_no': i, '_raw': text }
25+
def generate(self):
26+
for i in range(1, self.count + 1):
27+
text = 'Hello World %d' % i
28+
yield {'_time': time.time(), 'event_no': i, '_raw': text }
2929

3030
dispatch(GenerateHelloCommand, sys.argv, sys.stdin, sys.stdout, __name__)

examples/searchcommands_template/bin/generate.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,18 @@
66

77
@Configuration()
88
class %(command.title())Command(GeneratingCommand):
9-
""" %(synopsis)
9+
""" %(synopsis)
1010
11-
##Syntax
11+
##Syntax
1212
13-
%(syntax)
13+
%(syntax)
1414
15-
##Description
15+
##Description
1616
17-
%(description)
17+
%(description)
1818
19-
"""
20-
def generate(self):
19+
"""
20+
def generate(self):
2121
# Put your event code here
2222
pass
2323

examples/searchcommands_template/bin/stream.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@
77

88
@Configuration()
99
class %(command.title())Command(StreamingCommand):
10-
""" %(synopsis)
10+
""" %(synopsis)
1111
12-
##Syntax
12+
##Syntax
1313
14-
%(syntax)
14+
%(syntax)
1515
16-
##Description
16+
##Description
1717
18-
%(description)
18+
%(description)
1919
20-
"""
21-
def stream(self, events):
20+
"""
21+
def stream(self, events):
2222
# Put your event transformation code here
2323
pass
2424

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ def exclude(path):
186186
packages = ["splunklib",
187187
"splunklib.modularinput",
188188
"splunklib.searchcommands",
189-
"splunklib.searchcommands.csv"],
189+
"splunklib.searchcommands.splunk_csv"],
190190

191191
url="http://github.com/splunk/splunk-sdk-python",
192192

splunklib/binding.py

Lines changed: 4 additions & 2 deletions
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
@@ -464,7 +466,7 @@ def connect(self):
464466
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
465467
if self.scheme == "https":
466468
sock = ssl.wrap_socket(sock)
467-
sock.connect((self.host, self.port))
469+
sock.connect((socket.gethostbyname(self.host), self.port))
468470
return sock
469471

470472
@_authentication

splunklib/client.py

Lines changed: 85 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -517,25 +517,22 @@ def restart(self, timeout=None):
517517
:param timeout: A timeout period, in seconds.
518518
:type timeout: ``integer``
519519
"""
520+
msg = { "value": "Restart requested by " + self.username + "via the Splunk SDK for Python"}
521+
# This message will be deleted once the server actually restarts.
522+
self.messages.create(name="restart_required", **msg)
520523
result = self.post("server/control/restart")
521-
if timeout is None: return result
522-
start = datetime.now()
523-
diff = timedelta(seconds=10)
524-
while datetime.now() - start < diff:
525-
try:
526-
self.login() # Has the server gone down yet?
527-
sleep(0.3)
528-
except Exception:
529-
break # Server is down. Move on.
524+
if timeout is None:
525+
return result
530526
start = datetime.now()
531527
diff = timedelta(seconds=timeout)
532528
while datetime.now() - start < diff:
533529
try:
534-
self.login() # Awake yet?
535-
return result
530+
self.login()
531+
if not self.restart_required:
532+
return result
536533
except Exception, e:
537-
sleep(2)
538-
raise Exception, "Operation timed out."
534+
sleep(1)
535+
raise Exception, "Operation time out."
539536

540537
@property
541538
def restart_required(self):
@@ -1179,10 +1176,11 @@ def __getitem__(self, key):
11791176
# x[a,b] is translated to x.__getitem__( (a,b) ), so we
11801177
# have to extract values out.
11811178
key, ns = key
1179+
key = UrlEncoded(key, encode_slash=True)
11821180
response = self.get(key, owner=ns.owner, app=ns.app)
11831181
else:
1182+
key = UrlEncoded(key, encode_slash=True)
11841183
response = self.get(key)
1185-
11861184
entries = self._load_list(response)
11871185
if len(entries) > 1:
11881186
raise AmbiguousReferenceException("Found multiple entities named '%s'; please specify a namespace." % key)
@@ -1216,6 +1214,7 @@ def __iter__(self, **kwargs):
12161214
for entity in saved_searches:
12171215
print "Saved search named %s" % entity.name
12181216
"""
1217+
12191218
for item in self.iter(**kwargs):
12201219
yield item
12211220

@@ -1423,6 +1422,8 @@ def list(self, count=None, **kwargs):
14231422
return list(self.iter(count=count, **kwargs))
14241423

14251424

1425+
1426+
14261427
class Collection(ReadOnlyCollection):
14271428
"""A collection of entities.
14281429
@@ -1468,6 +1469,7 @@ class Collection(ReadOnlyCollection):
14681469
:class:`Collection` does no caching. Each call makes at least one
14691470
round trip to the server to fetch data.
14701471
"""
1472+
14711473
def create(self, name, **params):
14721474
"""Creates a new entity in this collection.
14731475
@@ -1542,6 +1544,7 @@ def delete(self, name, **params):
15421544
saved_searches.delete('my_saved_search')
15431545
assert 'my_saved_search' not in saved_searches
15441546
"""
1547+
name = UrlEncoded(name, encode_slash=True)
15451548
if 'namespace' in params:
15461549
namespace = params.pop('namespace')
15471550
params['owner'] = namespace.owner
@@ -1559,6 +1562,56 @@ def delete(self, name, **params):
15591562
raise
15601563
return self
15611564

1565+
def get(self, name="", owner=None, app=None, sharing=None, **query):
1566+
"""Performs a GET request to the server on the collection.
1567+
1568+
If *owner*, *app*, and *sharing* are omitted, this method takes a
1569+
default namespace from the :class:`Service` object for this :class:`Endpoint`.
1570+
All other keyword arguments are included in the URL as query parameters.
1571+
1572+
:raises AuthenticationError: Raised when the ``Service`` is not logged in.
1573+
:raises HTTPError: Raised when an error in the request occurs.
1574+
:param path_segment: A path segment relative to this endpoint.
1575+
:type path_segment: ``string``
1576+
:param owner: The owner context of the namespace (optional).
1577+
:type owner: ``string``
1578+
:param app: The app context of the namespace (optional).
1579+
:type app: ``string``
1580+
:param sharing: The sharing mode for the namespace (optional).
1581+
:type sharing: "global", "system", "app", or "user"
1582+
:param query: All other keyword arguments, which are used as query
1583+
parameters.
1584+
:type query: ``string``
1585+
:return: The response from the server.
1586+
:rtype: ``dict`` with keys ``body``, ``headers``, ``reason``,
1587+
and ``status``
1588+
1589+
Example:
1590+
1591+
import splunklib.client
1592+
s = client.service(...)
1593+
saved_searches = s.saved_searches
1594+
saved_searches.get("my/saved/search") == \\
1595+
{'body': ...a response reader object...,
1596+
'headers': [('content-length', '26208'),
1597+
('expires', 'Fri, 30 Oct 1998 00:00:00 GMT'),
1598+
('server', 'Splunkd'),
1599+
('connection', 'close'),
1600+
('cache-control', 'no-store, max-age=0, must-revalidate, no-cache'),
1601+
('date', 'Fri, 11 May 2012 16:30:35 GMT'),
1602+
('content-type', 'text/xml; charset=utf-8')],
1603+
'reason': 'OK',
1604+
'status': 200}
1605+
saved_searches.get('nonexistant/search') # raises HTTPError
1606+
s.logout()
1607+
saved_searches.get() # raises AuthenticationError
1608+
1609+
"""
1610+
name = UrlEncoded(name, encode_slash=True)
1611+
return super(Collection, self).get(name, owner, app, sharing, **query)
1612+
1613+
1614+
15621615

15631616
class ConfigurationFile(Collection):
15641617
"""This class contains all of the stanzas from one configuration file.
@@ -2075,27 +2128,32 @@ def __init__(self, service, kindmap=None):
20752128
Collection.__init__(self, service, PATH_INPUTS, item=Input)
20762129

20772130
def __getitem__(self, key):
2131+
# The key needed to retrieve the input needs it's parenthesis to be URL encoded
2132+
# based on the REST API for input
2133+
# <http://docs.splunk.com/Documentation/Splunk/latest/RESTAPI/RESTinput>
20782134
if isinstance(key, tuple) and len(key) == 2:
20792135
# Fetch a single kind
20802136
key, kind = key
2137+
key = UrlEncoded(key, encode_slash=True)
20812138
try:
20822139
response = self.get(self.kindpath(kind) + "/" + key)
20832140
entries = self._load_list(response)
20842141
if len(entries) > 1:
20852142
raise AmbiguousReferenceException("Found multiple inputs of kind %s named %s." % (kind, key))
20862143
elif len(entries) == 0:
2087-
raise KeyError((kind,key))
2144+
raise KeyError((key, kind))
20882145
else:
20892146
return entries[0]
20902147
except HTTPError as he:
20912148
if he.status == 404: # No entity matching kind and key
2092-
raise KeyError((kind,key))
2149+
raise KeyError((key, kind))
20932150
else:
20942151
raise
20952152
else:
20962153
# Iterate over all the kinds looking for matches.
20972154
kind = None
20982155
candidate = None
2156+
key = UrlEncoded(key, encode_slash=True)
20992157
for kind in self.kinds:
21002158
try:
21012159
response = self.get(kind + "/" + key)
@@ -2114,7 +2172,7 @@ def __getitem__(self, key):
21142172
else:
21152173
raise
21162174
if candidate is None:
2117-
raise KeyError(key) # Never found a match
2175+
raise KeyError(key) # Never found a match.
21182176
else:
21192177
return candidate
21202178

@@ -2187,11 +2245,14 @@ def create(self, name, kind, **kwargs):
21872245
# If we created an input with restrictToHost set, then
21882246
# its path will be <restrictToHost>:<name>, not just <name>,
21892247
# and we have to adjust accordingly.
2248+
2249+
# Url encodes the name of the entity.
2250+
name = UrlEncoded(name, encode_slash=True)
21902251
path = _path(
21912252
self.path + kindpath,
21922253
'%s:%s' % (kwargs['restrictToHost'], name) \
21932254
if kwargs.has_key('restrictToHost') else name
2194-
)
2255+
)
21952256
return Input(self.service, path, kind)
21962257

21972258
def delete(self, name, kind=None):
@@ -2268,7 +2329,7 @@ def itemmeta(self, kind):
22682329
def _get_kind_list(self, subpath=None):
22692330
if subpath is None:
22702331
subpath = []
2271-
2332+
22722333
kinds = []
22732334
response = self.get('/'.join(subpath))
22742335
content = _load_atom_entries(response)
@@ -2326,12 +2387,12 @@ def kindpath(self, kind):
23262387
:rtype: ``string``
23272388
"""
23282389
if kind in self.kinds:
2329-
return kind
2390+
return UrlEncoded(kind, skip_encode=True)
23302391
# Special cases
23312392
elif kind == 'tcp':
2332-
return 'tcp/raw'
2393+
return UrlEncoded('tcp/raw', skip_encode=True)
23332394
elif kind == 'splunktcp':
2334-
return 'tcp/cooked'
2395+
return UrlEncoded('tcp/cooked', skip_encode=True)
23352396
else:
23362397
raise ValueError("No such kind on server: %s" % kind)
23372398

@@ -2398,6 +2459,7 @@ def list(self, *kinds, **kwargs):
23982459
path = self.kindpath(kind)
23992460
logging.debug("Path for inputs: %s", path)
24002461
try:
2462+
path = UrlEncoded(path, skip_encode=True)
24012463
response = self.get(path, **kwargs)
24022464
except HTTPError, he:
24032465
if he.status == 404: # No inputs of this kind
@@ -2422,6 +2484,7 @@ def list(self, *kinds, **kwargs):
24222484
for kind in kinds:
24232485
response = None
24242486
try:
2487+
kind = UrlEncoded(kind, skip_encode=True)
24252488
response = self.get(self.kindpath(kind), search=search)
24262489
except HTTPError as e:
24272490
if e.status == 404:

splunklib/modularinput/argument.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class Argument(object):
4848
data_type_string = "STRING"
4949

5050
def __init__(self, name, description=None, validation=None,
51-
data_type=data_type_string, required_on_edit=False, required_on_create=False):
51+
data_type=data_type_string, required_on_edit=False, required_on_create=False, title=None):
5252
"""
5353
:param name: ``string``, identifier for this argument in Splunk.
5454
:param description: ``string``, human-readable description of the argument.
@@ -58,13 +58,15 @@ def __init__(self, name, description=None, validation=None,
5858
"data_type_boolean", "data_type_number", or "data_type_string".
5959
:param required_on_edit: ``Boolean``, whether this arg is required when editing an existing modular input of this kind.
6060
:param required_on_create: ``Boolean``, whether this arg is required when creating a modular input of this kind.
61+
:param title: ``String``, a human-readable title for the argument.
6162
"""
6263
self.name = name
6364
self.description = description
6465
self.validation = validation
6566
self.data_type = data_type
6667
self.required_on_edit = required_on_edit
6768
self.required_on_create = required_on_create
69+
self.title = title
6870

6971
def add_to_document(self, parent):
7072
"""Adds an ``Argument`` object to this ElementTree document.

splunklib/results.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ def _parse_results(self, stream):
246246
elem.clear()
247247

248248
elif elem.tag in ('text', 'v') and event == 'end':
249-
text = elem.text if elem.text is not None else ""
249+
text = "".join(elem.itertext())
250250
values.append(text.encode('utf8'))
251251
elem.clear()
252252

tests/modularinput/test_scheme.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,15 @@ def test_generate_xml_from_scheme(self):
5353
validation="is_pos_int('some_name')",
5454
data_type=Argument.data_type_number,
5555
required_on_edit=True,
56-
required_on_create=True
56+
required_on_create=True,
57+
title="Argument for ``test_scheme``"
5758
)
5859
scheme.add_argument(arg2)
5960

6061
constructed = scheme.to_xml()
62+
6163
expected = ET.parse(data_open("data/scheme_without_defaults.xml")).getroot()
64+
self.assertEqual("Argument for ``test_scheme``", arg2.title)
6265

6366
self.assertTrue(xml_compare(expected, constructed))
6467

0 commit comments

Comments
 (0)