splunk / splunk-sdk-python

Splunk Software Development Kit for Python
http://dev.splunk.com
Apache License 2.0
687 stars 369 forks source link

Add support for macros #516

Open Synse opened 1 year ago

Synse commented 1 year ago

This adds Service.macros for creating, retrieving, updating, and deleting macros.

Currently macros can be accessed via Configurations / ConfigurationFile but creating a proper Macros(Collection) and Macro(Entity) makes them much easier to work with.

Almost all of the code, including tests, is copied and modified from the existing code for saved searches (Service.saved_searches) so you can interact with macros in the same manner.

Tests

I added a few tests modeled after the existing ones for saved searches. The naming for macros with arguments means that test_create_with_args should probably move to another class (self.macro created by setUp is not used) but I didn't want to get carried away until I know there's a desire to merge this (or not).

I also had to remove the app parameter from the self.macro.acl_update call because it was failing with Argument "app" is not supported by this handler. It's not immediately clear to me why this is working for saved searches but not for macros.

$ pytest tests/test_macro.py --verbose
========================================================= test session starts =========================================================
platform linux -- Python 3.10.4, pytest-7.2.1, pluggy-1.0.0 -- /usr/local/python/3.10.4/bin/python
cachedir: .pytest_cache
rootdir: /workspaces/splunk-sdk-python, configfile: pytest.ini
plugins: anyio-3.6.2
collected 10 items                                                                                                                    

tests/test_macro.py::TestMacro::test_acl PASSED                                                                                 [ 10%]
tests/test_macro.py::TestMacro::test_acl_fails_without_owner PASSED                                                             [ 20%]
tests/test_macro.py::TestMacro::test_acl_fails_without_sharing PASSED                                                           [ 30%]
tests/test_macro.py::TestMacro::test_cannot_update_name PASSED                                                                  [ 40%]
tests/test_macro.py::TestMacro::test_create PASSED                                                                              [ 50%]
tests/test_macro.py::TestMacro::test_create_with_args PASSED                                                                    [ 60%]
tests/test_macro.py::TestMacro::test_delete PASSED                                                                              [ 70%]
tests/test_macro.py::TestMacro::test_name_collision PASSED                                                                      [ 80%]
tests/test_macro.py::TestMacro::test_no_equality PASSED                                                                         [ 90%]
tests/test_macro.py::TestMacro::test_update PASSED                                                                              [100%]

========================================================= 10 passed in 1.00s ==========================================================

Usage

As mentioned above, the usage is almost identical to Service.saved_searches for saved searches today.

List macros

>>> from splunklib.client import connect
>>> sc = connect(host='localhost', port=8089, username='admin', password='changed!')
>>> sc.macros
<splunklib.client.Macros object at 0x7f422daa5ba0>
>>> sc.macros.list()
[<splunklib.client.Macro object at 0x7f422d842050>, <splunklib.client.Macro object at 0x7f422d841f90>, <splunklib.client.Macro object at 0x7f422d841fc0>, <splunklib.client.Macro object at 0x7f422d841e40>, <splunklib.client.Macro object at 0x7f422d8424d0>, <splunklib.client.Macro object at 0x7f422d841f30>, <splunklib.client.Macro object at 0x7f422d8be4d0>, <splunklib.client.Macro object at 0x7f422d8be530>]
>>> [m.name for m in sc.macros.list()]
['audit_rexsearch', 'audit_searchlocal', 'audit_searchlocal(1)', 'comment(1)', 'histperc(3)', 'histperc(4)', 'set_local_host', 'truncate_search']
>>> 

Create a macro

>>> sc.macros.create(name='new_macro', definition='| eval foo="bar"')
<splunklib.client.Macro object at 0x7f422daa5ba0>
>>> sc.macros.list(search='new_macro')[0].content
{'definition': '| eval foo="bar"', 'disabled': '0', 'eai:appName': 'search', 'eai:userName': 'admin'}
>>> 

Enable/disable a macro

>>> macro = sc.macros.list(search='new_macro')[0]
>>> macro
<splunklib.client.Macro object at 0x7f422d841de0>
>>> macro.disabled
'0'
>>> macro.disable()
<splunklib.client.Macro object at 0x7f422d841de0>
>>> sc.macros.list(search='new_macro')[0].disabled
'1'
>>> macro.enable()
<splunklib.client.Macro object at 0x7f422d841de0>
>>> sc.macros.list(search='new_macro')[0].disabled
'0'

Update a macro

>>> macro = sc.macros.list(search='new_macro')[0]
>>> macro.content
{'definition': '| eval foo="bar"', 'disabled': '0', 'eai:appName': 'search', 'eai:userName': 'admin'}
>>> macro.update(definition='| eval updated="true"')
<splunklib.client.Macro object at 0x7f422daa5ba0>
>>> sc.macros.list(search='new_macro')[0].content
{'definition': '| eval updated="true"', 'disabled': '0', 'eai:appName': 'search', 'eai:userName': 'admin'}

Delete a macro

>>> macro = sc.macros.list(search='new_macro')[0]
>>> macro
<splunklib.client.Macro object at 0x7f422d841270>
>>> macro.delete()
{'status': 200, 'reason': 'OK', 'headers': [('Date', 'Mon, 13 Feb 2023 20:57:16 GMT'), ('Expires', 'Thu, 26 Oct 1978 00:00:00 GMT'), ('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0'), ('Content-Type', 'text/xml; charset=UTF-8'), ('X-Content-Type-Options', 'nosniff'), ('Content-Length', '1944'), ('Vary', 'Cookie, Authorization'), ('Connection', 'Close'), ('Set-Cookie', 'splunkd_8089=REDACTED; Path=/; Secure; HttpOnly; Max-Age=3600; Expires=Mon, 13 Feb 2023 21:57:16 GMT'), ('X-Frame-Options', 'SAMEORIGIN'), ('Server', 'Splunkd')], 'body': <splunklib.binding.ResponseReader object at 0x7f422d841cc0>}
>>> sc.macros.list(search='new_macro')
[]
Synse commented 1 year ago

It is also worth noting that the name/argument validation (e.g., Number of arguments provided (2) does not match with the number implied by the macro name (0)) appears to be occurring in the web UI so its possible to create macros that violate that naming convention.

I didn't add validation for that here as it should really be handled by the API and not in each SDK. That is, this is an issue with the API allowing creation of macros that don't conform to the expected naming conventions, not an SDK issue.

>>> sc = connect(host='localhost', port=8089, username='admin', password='changed!')
>>> sc.macros.create(name='no_args', definition='| eval foo="bar"', args='one, two')
<splunklib.client.Macro object at 0x7f8aac282860>
>>> sc.macros.list(search='no_args')[0].content
{'args': 'one, two', 'definition': '| eval foo="bar"', 'disabled': '0', 'eai:appName': 'search', 'eai:userName': 'admin'}
Synse commented 6 months ago

I've brought this PR up to date with develop and ensured tests still pass for the 2.0.0 release:

Screenshot 2024-03-21 at 11 44 41 AM

Tagging a few recent contributors to get some movement on this PR (it's been sitting over a year now).

@ashah-splunk, @PKing70, @maszyk99, @ichaer, or @akaila-splunk is there anything y'all can do to help move this along? :bow:

jonod8698 commented 5 months ago

+1 We need this for our use case!

pmeyerson commented 5 months ago

+1 would be helpful!

Synse commented 3 months ago

Thanks @maszyk99, let me know if there is anything I can assist with.

Synse commented 1 week ago

@maszyk99 any update on this? Anything I can do to move it along? :bow: