Skip to content

Commit 9ed4065

Browse files
author
Fred Ross
committed
Merge pull request #50 from splunk/fross/enumerate-input-kinds
Inputs now iterate over all kinds, dynamically drawn from the server.
2 parents 58f3101 + 686cc6f commit 9ed4065

File tree

2 files changed

+99
-67
lines changed

2 files changed

+99
-67
lines changed

splunklib/client.py

Lines changed: 50 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1633,31 +1633,24 @@ class Input(Entity):
16331633
typed input classes and is also used when the client does not recognize an
16341634
input kind."""
16351635
def __init__(self, service, path, kind=None, **kwargs):
1636+
# kind can be omitted (in which case it is inferred from the path)
1637+
# Otherwise, valid values are the paths from data/inputs ("udp",
1638+
# "monitor", "tcp/raw"), or two special cases: "tcp" (which is "tcp/raw")
1639+
# and "splunktcp" (which is "tcp/cooked").
16361640
Entity.__init__(self, service, path, **kwargs)
16371641
if kind is None:
1638-
for kind, kind_path in INPUT_KINDMAP.iteritems():
1639-
if kind_path in path:
1640-
self.kind = kind
1641-
break
1642+
path_segments = path.split('/')
1643+
i = path_segments.index('inputs')
1644+
self.kind = '/'.join(path_segments[i+1:-1])
16421645
assert self.kind is not None
16431646
else:
16441647
self.kind = kind
16451648

1646-
# Directory of known input kinds that maps from input kind to path relative
1647-
# to data/inputs, eg: inputs of kind 'splunktcp' map to a relative path
1648-
# of 'tcp/cooked' and therefore an endpoint path of 'data/inputs/tcp/cooked'.
1649-
INPUT_KINDMAP = {
1650-
'ad': "ad",
1651-
'monitor': "monitor",
1652-
'registry': "registry",
1653-
'script': "script",
1654-
'tcp': "tcp/raw",
1655-
'splunktcp': "tcp/cooked",
1656-
'udp': "udp",
1657-
'win-event-log-collections': "win-event-log-collections",
1658-
'win-perfmon': "win-perfmon",
1659-
'win-wmi-collections': "win-wmi-collections"
1660-
}
1649+
# Maintain compatibility with older kind labels
1650+
if self.kind == 'tcp/raw':
1651+
self.kind = 'tcp'
1652+
if self.kind == 'tcp/cooked':
1653+
self.kind = 'splunktcp'
16611654

16621655
# Inputs is a "kinded" collection, which is a heterogenous collection where
16631656
# each item is tagged with a kind, that provides a single merged view of all
@@ -1670,15 +1663,13 @@ class Inputs(Collection):
16701663

16711664
def __init__(self, service, kindmap=None):
16721665
Collection.__init__(self, service, PATH_INPUTS, item=Input)
1673-
self._kindmap = kindmap if kindmap is not None else INPUT_KINDMAP
1674-
1666+
16751667
def __getitem__(self, key):
16761668
if isinstance(key, tuple) and len(key) == 2:
16771669
# Fetch a single kind
16781670
kind, key = key
16791671
try:
1680-
kind_path = self._kindmap[kind]
1681-
response = self.get(kind_path + "/" + key)
1672+
response = self.get(self.kindpath(kind) + "/" + key)
16821673
entries = self._load_list(response)
16831674
if len(entries) > 1:
16841675
raise AmbiguousReferenceException("Found multiple inputs of kind %s named %s." % (kind, key))
@@ -1695,9 +1686,9 @@ def __getitem__(self, key):
16951686
# Iterate over all the kinds looking for matches.
16961687
kind = None
16971688
candidate = None
1698-
for kind, kind_path in self._kindmap.iteritems():
1689+
for kind in self.kinds:
16991690
try:
1700-
response = self.get(kind_path + "/" + key)
1691+
response = self.get(kind + "/" + key)
17011692
entries = self._load_list(response)
17021693
if len(entries) > 1:
17031694
raise AmbiguousReferenceException("Found multiple inputs of kind %s named %s." % (kind, key))
@@ -1729,9 +1720,9 @@ def __contains__(self, key):
17291720
# Without a kind, we want to minimize the number of round trips to the server, so we
17301721
# reimplement some of the behavior of __getitem__ in order to be able to stop searching
17311722
# on the first hit.
1732-
for kind, kind_path in self._kindmap.iteritems():
1723+
for kind in self.kinds:
17331724
try:
1734-
response = self.get(kind_path + "/" + key)
1725+
response = self.get(self.kindpath(kind) + "/" + key)
17351726
entries = self._load_list(response)
17361727
if len(entries) > 0:
17371728
return True
@@ -1758,8 +1749,9 @@ def create(self, kind, name, **kwargs):
17581749
:return: The new input.
17591750
"""
17601751
kindpath = self.kindpath(kind)
1761-
self.service.post(kindpath, name=name, **kwargs)
1762-
return Input(self.service, _path(kindpath, name), kind)
1752+
self.post(kindpath, name=name, **kwargs)
1753+
path = _path(self.path + kindpath, name)
1754+
return Input(self.service, path, kind)
17631755

17641756
def delete(self, kind, name=None):
17651757
"""Removes an input from the collection.
@@ -1779,18 +1771,40 @@ def itemmeta(self, kind):
17791771
content = _load_atom(response, MATCH_ENTRY_CONTENT)
17801772
return _parse_atom_metadata(content)
17811773

1774+
def _get_kind_list(self, subpath=[]):
1775+
kinds = []
1776+
response = self.get('/'.join(subpath))
1777+
content = _load_atom_entries(response)
1778+
for entry in content:
1779+
this_subpath = subpath + [entry.title]
1780+
if entry.title == 'all' or this_subpath == ['tcp','ssl']:
1781+
continue
1782+
elif 'create' in [x.rel for x in entry.link]:
1783+
kinds.append('/'.join(subpath + [entry.title]))
1784+
else:
1785+
subkinds = self._get_kind_list(subpath + [entry.title])
1786+
kinds.extend(subkinds)
1787+
return kinds
1788+
17821789
@property
1783-
def kinds(self):
1784-
"""Returns the list of input kinds that this collection may
1785-
contain."""
1786-
return self._kindmap.keys()
1790+
def kinds(self, subpath=[]):
1791+
"""Returns the list of input kinds that this collection contains."""
1792+
return self._get_kind_list()
17871793

17881794
def kindpath(self, kind):
17891795
"""Returns a path to the resources for a given input kind.
17901796
17911797
:param `kind`: The input kind.
17921798
"""
1793-
return self.path + self._kindmap[kind]
1799+
if kind in self.kinds:
1800+
return kind
1801+
# Special cases
1802+
elif kind == 'tcp':
1803+
return 'tcp/raw'
1804+
elif kind == 'splunktcp':
1805+
return 'tcp/cooked'
1806+
else:
1807+
raise ValueError("No such kind on server: %s" % kind)
17941808

17951809
def list(self, *kinds, **kwargs):
17961810
"""Returns a list of inputs that belong to the collection. You can also
@@ -1816,7 +1830,7 @@ def list(self, *kinds, **kwargs):
18161830
:param `kinds`: The input kinds to return (optional).
18171831
"""
18181832
if len(kinds) == 0:
1819-
kinds = self._kindmap.keys()
1833+
kinds = self.kinds
18201834
if len(kinds) == 1:
18211835
kind = kinds[0]
18221836
logging.debug("Inputs.list taking short circuit branch for single kind.")
@@ -1847,8 +1861,7 @@ def list(self, *kinds, **kwargs):
18471861
for kind in kinds:
18481862
response = None
18491863
try:
1850-
response = self.service.get(self.kindpath(kind),
1851-
search=search)
1864+
response = self.get(self.kindpath(kind), search=search)
18521865
except HTTPError as e:
18531866
if e.status == 404:
18541867
continue # No inputs of this kind

tests/test_input.py

Lines changed: 49 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,13 @@
1919

2020
import splunklib.client as client
2121

22-
test_inputs = [{'kind': 'tcp', 'name': '9999', 'host': 'sdk-test'},
23-
{'kind': 'udp', 'name': '9999', 'host': 'sdk-test'}]
24-
2522
class TestRead(testlib.TestCase):
2623
def test_read(self):
2724
inputs = self.service.inputs
2825
# count doesn't work on inputs; known problem tested for in
2926
# test_collection.py. This test will speed up dramatically
3027
# when that's fixed.
31-
for item in inputs.list(count=5):
28+
for item in inputs.list(count=5):
3229
self.check_entity(item)
3330
item.refresh()
3431
self.check_entity(item)
@@ -85,52 +82,74 @@ class TestInput(testlib.TestCase):
8582
def setUp(self):
8683
super(TestInput, self).setUp()
8784
inputs = self.service.inputs
85+
test_inputs = [{'kind': 'tcp', 'name': '9999', 'host': 'sdk-test'},
86+
{'kind': 'udp', 'name': '9999', 'host': 'sdk-test'}]
8887
self._test_entities = {}
89-
for test_input in test_inputs:
90-
if (test_input['kind'], test_input['name']) not in self.service.inputs:
91-
self._test_entities[test_input['kind']] = \
92-
inputs.create(**test_input)
88+
89+
base_port = 10000
90+
while True:
91+
if str(base_port) in inputs:
92+
base_port += 1
9393
else:
94-
self._test_entities[test_input['kind']] = \
95-
inputs[test_input['kind'], test_input['name']]
94+
break
95+
96+
self._test_entities['tcp'] = \
97+
inputs.create('tcp', str(base_port), host='sdk-test')
98+
self._test_entities['udp'] = \
99+
inputs.create('udp', str(base_port), host='sdk-test')
96100

97101
def tearDown(self):
98102
super(TestInput, self).tearDown()
99-
for test_input in test_inputs:
103+
for entity in self._test_entities.itervalues():
100104
try:
101105
self.service.inputs.delete(
102-
kind=test_input['kind'],
103-
name=test_input['name'])
106+
kind=entity.kind,
107+
name=entity.name)
104108
except KeyError:
105109
pass
106110

111+
def test_list(self):
112+
inputs = self.service.inputs
113+
input_list = inputs.list()
114+
self.assertTrue(len(input_list) > 0)
115+
for input in input_list:
116+
self.assertTrue(input.name is not None)
117+
118+
def test_lists_modular_inputs(self):
119+
if self.service.splunk_version[0] < 5:
120+
return # Modular inputs don't exist prior to 5.0
121+
else:
122+
inputs = self.service.inputs
123+
if ('test2','abcd') not in inputs:
124+
inputs.create('test2', 'abcd', field1='boris')
125+
input = inputs['test2', 'abcd']
126+
self.assertEqual(input.field1, 'boris')
127+
128+
107129
def test_create(self):
108130
inputs = self.service.inputs
109-
for test_input in test_inputs:
110-
kind, name, host = test_input['kind'], test_input['name'], test_input['host']
111-
entity = self._test_entities[kind]
112-
entity = inputs[kind, name]
131+
for entity in self._test_entities.itervalues():
113132
self.check_entity(entity)
114133
self.assertTrue(isinstance(entity, client.Input))
115-
self.assertEqual(entity.name, name)
116-
self.assertEqual(entity.kind, kind)
117-
self.assertEqual(entity.host, host)
134+
135+
def test_get_kind_list(self):
136+
inputs = self.service.inputs
137+
kinds = inputs._get_kind_list()
138+
self.assertTrue('tcp/raw' in kinds)
118139

119140
def test_read(self):
120141
inputs = self.service.inputs
121-
for test_input in test_inputs:
122-
kind, name = test_input['kind'], test_input['name']
123-
this_entity = self._test_entities[kind]
142+
for this_entity in self._test_entities.itervalues():
143+
kind, name = this_entity.kind, this_entity.name
124144
read_entity = inputs[kind, name]
125145
self.assertEqual(this_entity.kind, read_entity.kind)
126146
self.assertEqual(this_entity.name, read_entity.name)
127147
self.assertEqual(this_entity.host, read_entity.host)
128148

129149
def test_update(self):
130150
inputs = self.service.inputs
131-
for test_input in test_inputs:
132-
kind, name = test_input['kind'], test_input['name']
133-
entity = inputs[kind, name]
151+
for entity in self._test_entities.itervalues():
152+
kind, name = entity.kind, entity.name
134153
kwargs = {'host': 'foo', 'sourcetype': 'bar'}
135154
entity.update(**kwargs)
136155
entity.refresh()
@@ -139,10 +158,10 @@ def test_update(self):
139158

140159
def test_delete(self):
141160
inputs = self.service.inputs
142-
remaining = len(test_inputs)-1
143-
for test_input in test_inputs:
144-
kind, name = test_input['kind'], test_input['name']
145-
input_entity = self.service.inputs[kind,name]
161+
remaining = len(self._test_entities)-1
162+
for input_entity in self._test_entities.itervalues():
163+
name = input_entity.name
164+
kind = input_entity.kind
146165
self.assertTrue(name in inputs)
147166
self.assertTrue((kind,name) in inputs)
148167
if remaining == 0:

0 commit comments

Comments
 (0)