Skip to content

Commit e7ba1c1

Browse files
authored
Merge pull request splunk#589 from splunk/test-macros-pr
Test macros pr
2 parents eeb1ecf + 6de12b1 commit e7ba1c1

File tree

2 files changed

+259
-1
lines changed

2 files changed

+259
-1
lines changed

splunklib/client.py

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
PATH_JOBS = "search/jobs/"
102102
PATH_JOBS_V2 = "search/v2/jobs/"
103103
PATH_LOGGER = "/services/server/logger/"
104+
PATH_MACROS = "configs/conf-macros/"
104105
PATH_MESSAGES = "messages/"
105106
PATH_MODULAR_INPUTS = "data/modular-inputs"
106107
PATH_ROLES = "authorization/roles/"
@@ -667,6 +668,15 @@ def saved_searches(self):
667668
"""
668669
return SavedSearches(self)
669670

671+
@property
672+
def macros(self):
673+
"""Returns the collection of macros.
674+
675+
:return: A :class:`Macros` collection of :class:`Macro`
676+
entities.
677+
"""
678+
return Macros(self)
679+
670680
@property
671681
def settings(self):
672682
"""Returns the configuration settings for this instance of Splunk.
@@ -3440,6 +3450,90 @@ def create(self, name, search, **kwargs):
34403450
return Collection.create(self, name, search=search, **kwargs)
34413451

34423452

3453+
class Macro(Entity):
3454+
"""This class represents a search macro."""
3455+
def __init__(self, service, path, **kwargs):
3456+
Entity.__init__(self, service, path, **kwargs)
3457+
3458+
@property
3459+
def args(self):
3460+
"""Returns the macro arguments.
3461+
:return: The macro arguments.
3462+
:rtype: ``string``
3463+
"""
3464+
return self._state.content.get('args', '')
3465+
3466+
@property
3467+
def definition(self):
3468+
"""Returns the macro definition.
3469+
:return: The macro definition.
3470+
:rtype: ``string``
3471+
"""
3472+
return self._state.content.get('definition', '')
3473+
3474+
@property
3475+
def errormsg(self):
3476+
"""Returns the validation error message for the macro.
3477+
:return: The validation error message for the macro.
3478+
:rtype: ``string``
3479+
"""
3480+
return self._state.content.get('errormsg', '')
3481+
3482+
@property
3483+
def iseval(self):
3484+
"""Returns the eval-based definition status of the macro.
3485+
:return: The iseval value for the macro.
3486+
:rtype: ``string``
3487+
"""
3488+
return self._state.content.get('iseval', '0')
3489+
3490+
def update(self, definition=None, **kwargs):
3491+
"""Updates the server with any changes you've made to the current macro
3492+
along with any additional arguments you specify.
3493+
:param `definition`: The macro definition (optional).
3494+
:type definition: ``string``
3495+
:param `kwargs`: Additional arguments (optional). Available parameters are:
3496+
'disabled', 'iseval', 'validation', and 'errormsg'.
3497+
:type kwargs: ``dict``
3498+
:return: The :class:`Macro`.
3499+
"""
3500+
# Updates to a macro *require* that the definition be
3501+
# passed, so we pass the current definition if a value wasn't
3502+
# provided by the caller.
3503+
if definition is None: definition = self.content.definition
3504+
Entity.update(self, definition=definition, **kwargs)
3505+
return self
3506+
3507+
@property
3508+
def validation(self):
3509+
"""Returns the validation expression for the macro.
3510+
:return: The validation expression for the macro.
3511+
:rtype: ``string``
3512+
"""
3513+
return self._state.content.get('validation', '')
3514+
3515+
3516+
class Macros(Collection):
3517+
"""This class represents a collection of macros. Retrieve this
3518+
collection using :meth:`Service.macros`."""
3519+
def __init__(self, service):
3520+
Collection.__init__(
3521+
self, service, PATH_MACROS, item=Macro)
3522+
3523+
def create(self, name, definition, **kwargs):
3524+
""" Creates a macro.
3525+
:param name: The name for the macro.
3526+
:type name: ``string``
3527+
:param definition: The macro definition.
3528+
:type definition: ``string``
3529+
:param kwargs: Additional arguments (optional). Available parameters are:
3530+
'disabled', 'iseval', 'validation', and 'errormsg'.
3531+
:type kwargs: ``dict``
3532+
:return: The :class:`Macros` collection.
3533+
"""
3534+
return Collection.create(self, name, definition=definition, **kwargs)
3535+
3536+
34433537
class Settings(Entity):
34443538
"""This class represents configuration settings for a Splunk service.
34453539
Retrieve this collection using :meth:`Service.settings`."""
@@ -3905,4 +3999,4 @@ def batch_save(self, *documents):
39053999
data = json.dumps(documents)
39064000

39074001
return json.loads(
3908-
self._post('batch_save', headers=KVStoreCollectionData.JSON_HEADER, body=data).body.read().decode('utf-8'))
4002+
self._post('batch_save', headers=KVStoreCollectionData.JSON_HEADER, body=data).body.read().decode('utf-8'))

tests/test_macro.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
#!/usr/bin/env python
2+
#
3+
# Copyright 2011-2015 Splunk, Inc.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License"): you may
6+
# not use this file except in compliance with the License. You may obtain
7+
# a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
# License for the specific language governing permissions and limitations
15+
# under the License.
16+
17+
from __future__ import absolute_import
18+
from tests import testlib
19+
import logging
20+
21+
import splunklib.client as client
22+
23+
import pytest
24+
25+
@pytest.mark.smoke
26+
class TestMacro(testlib.SDKTestCase):
27+
def setUp(self):
28+
super(TestMacro, self).setUp()
29+
macros = self.service.macros
30+
logging.debug("Macros namespace: %s", macros.service.namespace)
31+
self.macro_name = testlib.tmpname()
32+
definition = '| eval test="123"'
33+
self.macro = macros.create(self.macro_name, definition)
34+
35+
def tearDown(self):
36+
super(TestMacro, self).setUp()
37+
for macro in self.service.macros:
38+
if macro.name.startswith('delete-me'):
39+
self.service.macros.delete(macro.name)
40+
41+
def check_macro(self, macro):
42+
self.check_entity(macro)
43+
expected_fields = ['definition',
44+
'iseval',
45+
'args',
46+
'validation',
47+
'errormsg']
48+
for f in expected_fields:
49+
macro[f]
50+
is_eval = macro.iseval
51+
self.assertTrue(is_eval == '1' or is_eval == '0')
52+
53+
def test_create(self):
54+
self.assertTrue(self.macro_name in self.service.macros)
55+
self.check_macro(self.macro)
56+
57+
def test_create_with_args(self):
58+
macro_name = testlib.tmpname() + '(1)'
59+
definition = '| eval value="$value$"'
60+
kwargs = {
61+
'args': 'value',
62+
'validation': '$value$ > 10',
63+
'errormsg': 'value must be greater than 10'
64+
}
65+
macro = self.service.macros.create(macro_name, definition=definition, **kwargs)
66+
self.assertTrue(macro_name in self.service.macros)
67+
self.check_macro(macro)
68+
self.assertEqual(macro.iseval, '0')
69+
self.assertEqual(macro.args, kwargs.get('args'))
70+
self.assertEqual(macro.validation, kwargs.get('validation'))
71+
self.assertEqual(macro.errormsg, kwargs.get('errormsg'))
72+
self.service.macros.delete(macro_name)
73+
74+
def test_delete(self):
75+
self.assertTrue(self.macro_name in self.service.macros)
76+
self.service.macros.delete(self.macro_name)
77+
self.assertFalse(self.macro_name in self.service.macros)
78+
self.assertRaises(client.HTTPError,
79+
self.macro.refresh)
80+
81+
def test_update(self):
82+
new_definition = '| eval updated="true"'
83+
self.macro.update(definition=new_definition)
84+
self.macro.refresh()
85+
self.assertEqual(self.macro['definition'], new_definition)
86+
87+
is_eval = testlib.to_bool(self.macro['iseval'])
88+
self.macro.update(iseval=not is_eval)
89+
self.macro.refresh()
90+
self.assertEqual(testlib.to_bool(self.macro['iseval']), not is_eval)
91+
92+
def test_cannot_update_name(self):
93+
new_name = self.macro_name + '-alteration'
94+
self.assertRaises(client.IllegalOperationException,
95+
self.macro.update, name=new_name)
96+
97+
def test_name_collision(self):
98+
opts = self.opts.kwargs.copy()
99+
opts['owner'] = '-'
100+
opts['app'] = '-'
101+
opts['sharing'] = 'user'
102+
service = client.connect(**opts)
103+
logging.debug("Namespace for collision testing: %s", service.namespace)
104+
macros = service.macros
105+
name = testlib.tmpname()
106+
107+
dispatch1 = '| eval macro_one="1"'
108+
dispatch2 = '| eval macro_two="2"'
109+
namespace1 = client.namespace(app='search', sharing='app')
110+
namespace2 = client.namespace(owner='admin', app='search', sharing='user')
111+
new_macro2 = macros.create(
112+
name, dispatch2,
113+
namespace=namespace1)
114+
new_macro1 = macros.create(
115+
name, dispatch1,
116+
namespace=namespace2)
117+
118+
self.assertRaises(client.AmbiguousReferenceException,
119+
macros.__getitem__, name)
120+
macro1 = macros[name, namespace1]
121+
self.check_macro(macro1)
122+
macro1.update(**{'definition': '| eval number=1'})
123+
macro1.refresh()
124+
self.assertEqual(macro1['definition'], '| eval number=1')
125+
macro2 = macros[name, namespace2]
126+
macro2.update(**{'definition': '| eval number=2'})
127+
macro2.refresh()
128+
self.assertEqual(macro2['definition'], '| eval number=2')
129+
self.check_macro(macro2)
130+
131+
def test_no_equality(self):
132+
self.assertRaises(client.IncomparableException,
133+
self.macro.__eq__, self.macro)
134+
135+
def test_acl(self):
136+
self.assertEqual(self.macro.access["perms"], None)
137+
self.macro.acl_update(sharing="app", owner="admin", **{"perms.read": "admin, nobody"})
138+
self.assertEqual(self.macro.access["owner"], "admin")
139+
self.assertEqual(self.macro.access["sharing"], "app")
140+
self.assertEqual(self.macro.access["perms"]["read"], ['admin', 'nobody'])
141+
142+
def test_acl_fails_without_sharing(self):
143+
self.assertRaisesRegex(
144+
ValueError,
145+
"Required argument 'sharing' is missing.",
146+
self.macro.acl_update,
147+
owner="admin", app="search", **{"perms.read": "admin, nobody"}
148+
)
149+
150+
def test_acl_fails_without_owner(self):
151+
self.assertRaisesRegex(
152+
ValueError,
153+
"Required argument 'owner' is missing.",
154+
self.macro.acl_update,
155+
sharing="app", app="search", **{"perms.read": "admin, nobody"}
156+
)
157+
158+
159+
if __name__ == "__main__":
160+
try:
161+
import unittest2 as unittest
162+
except ImportError:
163+
import unittest
164+
unittest.main()

0 commit comments

Comments
 (0)