Skip to content

Commit e794d4d

Browse files
author
Frederick Ross
committed
Resolved comments from Itay's code review.
Code review at #46
1 parent 556f388 commit e794d4d

File tree

3 files changed

+70
-19
lines changed

3 files changed

+70
-19
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* Added Indexes.default() which returns the name of the default index that data will be submitted into.
1616
* Connecting with a preexisting token works whether the token begins with 'Splunk ' or not;
1717
the SDK will handle either case correctly.
18-
* Added .isReady() and .isDone() methods to Job to make it easy to loop until either point as been reached.
18+
* Added .is_ready() and .is_done() methods to Job to make it easy to loop until either point as been reached.
1919
* Expanded endpoint coverage. Now at parity with the Java SDK.
2020
* Replaced ResultsReader with something shorter. Iteration now
2121
results either Message objects or dicts, and moved preview from

splunklib/binding.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,8 @@ class Context(object):
385385
def __init__(self, handler=None, **kwargs):
386386
self.http = HttpLib(handler)
387387
self.token = kwargs.get("token", NoAuthenticationToken)
388+
if self.token is None: # In case someone explicitly passes token=None
389+
self.token = NoAuthenticationToken
388390
self.scheme = kwargs.get("scheme", DEFAULT_SCHEME)
389391
self.host = kwargs.get("host", DEFAULT_HOST)
390392
self.port = int(kwargs.get("port", DEFAULT_PORT))
@@ -698,7 +700,7 @@ def login(self):
698700
# Then issue requests...
699701
"""
700702
if self.token is not NoAuthenticationToken and \
701-
(self.username == "" and self.password == ""):
703+
(self.username and self.password):
702704
# If we were passed a session token, but no username or
703705
# password, then login is a nop, since we're automatically
704706
# logged in.

splunklib/client.py

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,14 @@ def restart(self, timeout=None):
418418
"""
419419
result = self.get("server/control/restart")
420420
if timeout is None: return result
421-
sleep(5)
421+
start = datetime.now()
422+
diff = timedelta(seconds=10)
423+
while datetime.now() - start < diff:
424+
try:
425+
self.login() # Has the server gone down yet?
426+
sleep(0.3)
427+
except Exception:
428+
break # Server is down. Move on.
422429
start = datetime.now()
423430
diff = timedelta(seconds=timeout)
424431
while datetime.now() - start < diff:
@@ -735,6 +742,17 @@ def _run_method(self, path_segment, **kwargs):
735742
return rec.content
736743

737744
def _proper_namespace(self, owner=None, app=None, sharing=None):
745+
"""Produce a namespace sans wildcards for use in entity requests.
746+
747+
This method handles the case of two entities with the same name in different namespaces
748+
showing up due to wildcards in the service's namespace. We replace the wildcards with the
749+
namespace of the entity we want.
750+
"""
751+
:param owner:
752+
:param app:
753+
:param sharing:
754+
:return:
755+
"""
738756
if owner is None and app is None and sharing is None and\
739757
(self.service.namespace.owner == '-' or self.service.namespace.app == '-'):
740758
# If no namespace is specified and there are wildcards in the service's namespace,
@@ -1303,13 +1321,11 @@ def list(self, count=None, **kwargs):
13031321
return list(self.iter(count=count, **kwargs))
13041322
13051323
def names(self, count=None, **kwargs):
1306-
"""Return a list over the names of entities in this collection.
1324+
"""Return a list of the names of all the entities in this collection.
13071325

1308-
There is no laziness in this function. The entire collection
1309-
is loaded at once and returned as a list. This function makes
1310-
a single roundtrip to the server, plus at most two more if
1311-
autologin is enabled. There is no caching: every call makes at
1312-
least one round trip.
1326+
The entire list is loaded at once in a single roundtrip to the server,
1327+
plus at most two more if autologin is enabled. There is no caching:
1328+
every call makes at least one round trip.
13131329

13141330
:param `count`: The maximum number of names to return (optional).
13151331
:param `offset`: The offset of the first name to return (optional).
@@ -1330,7 +1346,6 @@ class ConfigurationFile(Collection):
13301346
# Collection, since it is being created as the elements of a
13311347
# Configurations, which is a Collection subclass.
13321348
def __init__(self, service, path, **kwargs):
1333-
#assert 'properties' not in path
13341349
Collection.__init__(self, service, path, item=Stanza)
13351350
self.name = kwargs['state']['title']
13361351
@@ -1347,9 +1362,9 @@ def __init__(self, service):
13471362
raise ValueError("Configurations cannot have wildcards in namespace.")
13481363
13491364
def __getitem__(self, key):
1350-
# This has to be overridden because we get multiple values
1351-
# back as a matter of course, unlike for most other endpoints
1352-
# where multiple values means a name conflict.
1365+
# The configurations endpoint returns multiple entities when we ask for a single file.
1366+
# This screws up the default implementation of __getitem__ from Collection, which thinks
1367+
# that multiple entities means a name collision, so we have to override it here.
13531368
try:
13541369
response = self.get(key)
13551370
return ConfigurationFile(self.service, PATH_CONF % key, state={'title': key})
@@ -1409,6 +1424,9 @@ def submit(self, stanza):
14091424
return self
14101425
14111426
def __len__(self):
1427+
# The stanza endpoint returns all the keys at the same level in the XML as the eai information
1428+
# and 'disabled', so to get an accurate length, we have to filter those out and have just
1429+
# the stanza keys.
14121430
return len([x for x in self._state.content.keys()
14131431
if not x.startswith('eai') and x != 'disabled'])
14141432
@@ -2263,6 +2281,11 @@ def acknowledge(self):
22632281
22642282
@property
22652283
def alert_count(self):
2284+
"""Return the number of alerts fired by this saved search.
2285+
2286+
:return: The number of alerts fired by this saved search.
2287+
:rtype: integer
2288+
"""
22662289
return int(self._state.content.get('triggered_alert_count', 0))
22672290
22682291
def dispatch(self, **kwargs):
@@ -2383,12 +2406,8 @@ def update(self, **kwargs):
23832406
class User(Entity):
23842407
@property
23852408
def role_entities(self):
2386-
role_names = self.content.roles
2387-
roles = []
2388-
for name in role_names:
2389-
role = self.service.roles[name]
2390-
roles.append(role)
2391-
return roles
2409+
"""Return a list of entities representing all the roles assigned to this user."""
2410+
return [self.service.roles[name] for name in self.content.roles]
23922411
23932412
# Splunk automatically lowercases new user names so we need to match that
23942413
# behavior here to ensure that the subsequent member lookup works correctly.
@@ -2455,6 +2474,21 @@ def delete(self, name):
24552474
24562475
class Role(Entity):
24572476
def grant(self, *capabilities_to_grant):
2477+
"""Grant additional capabilities to this role.
2478+
2479+
The capabilities are strings. You can get the complete list from
2480+
Service.capabilities, or from the /authorization/capabilities
2481+
endpoint in Splunk (or just look in splunkweb).
2482+
2483+
**Example**::
2484+
2485+
service = client.connect(...)
2486+
role = service.roles['somerole']
2487+
role.grant('change_own_password', 'search')
2488+
2489+
:param capabilities_to_grant: Zero or more capabilities to grant this role.
2490+
:return: The Role entity.
2491+
"""
24582492
possible_capabilities = self.service.capabilities
24592493
for capability in capabilities_to_grant:
24602494
if capability not in possible_capabilities:
@@ -2465,6 +2499,21 @@ def grant(self, *capabilities_to_grant):
24652499
return self
24662500
24672501
def revoke(self, *capabilities_to_revoke):
2502+
"""Revoke zero or more capabilities from this role.
2503+
2504+
The capabilities are strings. You can get the complete list from
2505+
Service.capabilities, or from the /authorization/capabilities
2506+
endpoint in Splunk (or just look in splunkweb).
2507+
2508+
**Example**::
2509+
2510+
service = client.connect(...)
2511+
role = service.roles['somerole']
2512+
role.revoke('change_own_password', 'search')
2513+
2514+
:param capabilities_to_revoke: Zero or more capabilities to revoke from this role.
2515+
:return: The Role entity
2516+
"""
24682517
possible_capabilities = self.service.capabilities
24692518
for capability in capabilities_to_revoke:
24702519
if capability not in possible_capabilities:

0 commit comments

Comments
 (0)