Skip to content

Commit 4527a99

Browse files
author
Frederick Ross
committed
Inputs now iterate over all kinds, dynamically drawn from the server.
1 parent 58f3101 commit 4527a99

File tree

2 files changed

+95
-65
lines changed

2 files changed

+95
-65
lines changed

splunklib/client.py

Lines changed: 46 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1635,29 +1635,18 @@ class Input(Entity):
16351635
def __init__(self, service, path, kind=None, **kwargs):
16361636
Entity.__init__(self, service, path, **kwargs)
16371637
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
1638+
path_segments = path.split('/')
1639+
i = path_segments.index('inputs')
1640+
self.kind = '/'.join(path_segments[i+1:-1])
16421641
assert self.kind is not None
16431642
else:
16441643
self.kind = kind
16451644

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-
}
1645+
# Maintain compatibility with older kind labels
1646+
if self.kind == 'tcp/raw':
1647+
self.kind = 'tcp'
1648+
if self.kind == 'tcp/cooked':
1649+
self.kind = 'splunktcp'
16611650

16621651
# Inputs is a "kinded" collection, which is a heterogenous collection where
16631652
# each item is tagged with a kind, that provides a single merged view of all
@@ -1670,14 +1659,13 @@ class Inputs(Collection):
16701659

16711660
def __init__(self, service, kindmap=None):
16721661
Collection.__init__(self, service, PATH_INPUTS, item=Input)
1673-
self._kindmap = kindmap if kindmap is not None else INPUT_KINDMAP
1674-
1662+
16751663
def __getitem__(self, key):
16761664
if isinstance(key, tuple) and len(key) == 2:
16771665
# Fetch a single kind
16781666
kind, key = key
16791667
try:
1680-
kind_path = self._kindmap[kind]
1668+
kind_path = self.kindpath(kind)
16811669
response = self.get(kind_path + "/" + key)
16821670
entries = self._load_list(response)
16831671
if len(entries) > 1:
@@ -1695,9 +1683,9 @@ def __getitem__(self, key):
16951683
# Iterate over all the kinds looking for matches.
16961684
kind = None
16971685
candidate = None
1698-
for kind, kind_path in self._kindmap.iteritems():
1686+
for kind in self.kinds:
16991687
try:
1700-
response = self.get(kind_path + "/" + key)
1688+
response = self.get(kind + "/" + key)
17011689
entries = self._load_list(response)
17021690
if len(entries) > 1:
17031691
raise AmbiguousReferenceException("Found multiple inputs of kind %s named %s." % (kind, key))
@@ -1729,9 +1717,9 @@ def __contains__(self, key):
17291717
# Without a kind, we want to minimize the number of round trips to the server, so we
17301718
# reimplement some of the behavior of __getitem__ in order to be able to stop searching
17311719
# on the first hit.
1732-
for kind, kind_path in self._kindmap.iteritems():
1720+
for kind in self.kinds:
17331721
try:
1734-
response = self.get(kind_path + "/" + key)
1722+
response = self.get(self.kindpath(kind) + "/" + key)
17351723
entries = self._load_list(response)
17361724
if len(entries) > 0:
17371725
return True
@@ -1758,8 +1746,9 @@ def create(self, kind, name, **kwargs):
17581746
:return: The new input.
17591747
"""
17601748
kindpath = self.kindpath(kind)
1761-
self.service.post(kindpath, name=name, **kwargs)
1762-
return Input(self.service, _path(kindpath, name), kind)
1749+
self.post(kindpath, name=name, **kwargs)
1750+
path = _path(self.path + kindpath, name)
1751+
return Input(self.service, path, kind)
17631752

17641753
def delete(self, kind, name=None):
17651754
"""Removes an input from the collection.
@@ -1779,18 +1768,40 @@ def itemmeta(self, kind):
17791768
content = _load_atom(response, MATCH_ENTRY_CONTENT)
17801769
return _parse_atom_metadata(content)
17811770

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

17881791
def kindpath(self, kind):
17891792
"""Returns a path to the resources for a given input kind.
17901793
17911794
:param `kind`: The input kind.
17921795
"""
1793-
return self.path + self._kindmap[kind]
1796+
if kind in self.kinds:
1797+
return kind
1798+
# Special cases
1799+
elif kind == 'tcp':
1800+
return 'tcp/raw'
1801+
elif kind == 'splunktcp':
1802+
return 'tcp/cooked'
1803+
else:
1804+
raise ValueError("No such kind on server: %s" % kind)
17941805

17951806
def list(self, *kinds, **kwargs):
17961807
"""Returns a list of inputs that belong to the collection. You can also
@@ -1816,7 +1827,7 @@ def list(self, *kinds, **kwargs):
18161827
:param `kinds`: The input kinds to return (optional).
18171828
"""
18181829
if len(kinds) == 0:
1819-
kinds = self._kindmap.keys()
1830+
kinds = self.kinds
18201831
if len(kinds) == 1:
18211832
kind = kinds[0]
18221833
logging.debug("Inputs.list taking short circuit branch for single kind.")
@@ -1847,7 +1858,7 @@ def list(self, *kinds, **kwargs):
18471858
for kind in kinds:
18481859
response = None
18491860
try:
1850-
response = self.service.get(self.kindpath(kind),
1861+
response = self.get(self.kindpath(kind),
18511862
search=search)
18521863
except HTTPError as e:
18531864
if e.status == 404:

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)