-
Notifications
You must be signed in to change notification settings - Fork 34
feat: provide support for custom search command #1534
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 21 commits
84c9d33
b5989f6
039fe0d
7432330
3802847
fda2c7f
85be57e
d73930c
d4e3f3e
181da69
2e288e7
d90a475
f8d6ce3
1425f9b
1ef0399
b68d9a6
3951671
b3a42b6
9af4085
2866cf9
312ee3c
8119835
afc6bbb
5f44b3a
4f0a32f
8823338
a7ec116
9986ec3
e47b2cd
1adadbb
ff8da5b
24d9860
b9a73e3
80d43e6
d9c8229
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,218 @@ | ||
| # Custom Search Command | ||
|
|
||
| Custom search commands are user-defined [SPL](https://docs.splunk.com/Splexicon:SPL) (Splunk Search Processing Language) commands that enable users to add custom functionality to their Splunk searches. | ||
|
|
||
| There are two versions of implementing custom search commands: | ||
|
|
||
| - Version 1: This uses the InterSplunk module and has been deprecated. (It is not recommended to use the Version 1 protocol.) | ||
| - Version 2: Introduced in Splunk 6.3.0, this version is faster, more scalable, and has replaced Version 1. | ||
|
|
||
| ## Generation of custom search command | ||
|
|
||
| A new tag has been introduced in globalConfig (same indent level as of `meta` tag) named `customSearchCommand` where you need to define the configuration for the custom search command. | ||
|
|
||
| ### Minimal definition | ||
|
|
||
| ```json | ||
| "customSearchCommand": [ | ||
| { | ||
| "commandName": "mycommandname", | ||
| "fileName": "mycommandlogic.py", | ||
| "commandType": "generating", | ||
| "arguments": [ | ||
| { | ||
| "name": "argument_name", | ||
| "validate": { | ||
| "type": "Fieldname" | ||
| }, | ||
| "required": true | ||
| }, | ||
| { | ||
| "name": "argument_two" | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| ``` | ||
|
|
||
| This configuration will generate a template Python file named `mycommandname.py`, which imports logic from the `mycommandlogic.py` file and automatically updates the `commands.conf` file as shown below: | ||
|
|
||
| ``` | ||
| [mycommandname] | ||
| filename = mycommandname.py | ||
| chunked = true | ||
| python.version = python3 | ||
| ``` | ||
|
|
||
| **NOTE:** | ||
| If the file specified in the `fileName` field does not exist in the `<YOUR_ADDON/bin>` directory, the build will fail. | ||
|
|
||
| ### Attributes for `customSearchCommand` tag | ||
|
|
||
| | Property | Type | Description | | ||
| | ------------------------ | ------ | ------------------------------------ | | ||
| | commandName<span class="required-asterisk">\*</span> | string | Name of the custom search command | | ||
| | fileName<span class="required-asterisk">\*</span> | string | Name of the Python file which contains logic of custom search command | | ||
| | commandType<span class="required-asterisk">\*</span> | string | Specify type of custom search command. Four types of commands are allowed, `streaming`,`generating`,`reporting` and `eventing`. | | ||
| | version | number | Specifies the protocol being used (default is 2). | | ||
kkedziak-splunk marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| | arguments<span class="required-asterisk">\*</span> | object | Arguments which can be passed to custom search command. | | ||
| | requiredSearchAssistant | boolean | Specifies whether search assistance is required for the custom search command. Default: false. | | ||
| | usage | string | Defines the usage of custom search command. It can be one of `public`, `private` and `deprecated`. | | ||
| | description | string | Provide description of the custom search command. | | ||
| | syntax | string | Provide syntax for custom search command | | ||
|
|
||
| To generate a custom search command, the following attributes must be defined in globalConfig: `commandName`, `commandType`, `fileName`, and `arguments`. Based on the provided commandType, UCC will generate a template Python file and integrate the user-defined logic into it. | ||
|
|
||
| If `requiredSearchAssistant` is set to True, the `syntax`, `description`, and `usage` attributes are mandatory, as they are essential for generating `searchbnf.conf` file. | ||
|
|
||
| **NOTE:** | ||
| The user-defined Python file must include specific functions based on the command type: | ||
|
|
||
| - For `Generating` command, the Python file must include a `generate` function. | ||
| - For `Streaming` command, the Python file must include a `stream` function. | ||
| - For `Eventing` command, the Python file must include a `transform` function. | ||
| - For `Reporting` command, the Python file must include a `reduce` function, and optionally a `map` function if a streaming pre-operation is required. | ||
|
|
||
| ## Arguments | ||
|
|
||
| | Property | Type | Description | | ||
| | --------------------------------------------------------------------- | ------ | ------------------------------------------------------- | | ||
| | name<span class="required-asterisk">\*</span> | string | Name of the argument | | ||
| | defaultValue | string/number | Default value of the argument. | | ||
| | required | string | Specify if the argument is required or not. | | ||
| | validate | object | Specify validation for the argument. It can be any of `Integer`, `Float`, `Boolean`, `RegularExpression` or `FieldName`. | | ||
|
|
||
| UCC currently supports five types of validations provided by `splunklib` library: | ||
|
|
||
| - IntegerValidator | ||
| + you can optionally define `minimum` and `maximum` properties. | ||
| - FloatValidator | ||
| + you can optionally define `minimum` and `maximum` properties. | ||
| - BooleanValidator | ||
| + no additional properties required. | ||
| - RegularExpressionValidator | ||
| + no additional properties required. | ||
| - FieldnameValidator | ||
| + no additional properties required. | ||
|
|
||
| For more information, refer [splunklib API docs](https://splunk-python-sdk.readthedocs.io/en/latest/searchcommands.html) | ||
|
|
||
| For example: | ||
|
|
||
| ```json | ||
| "arguments": [ | ||
| { | ||
| "name": "count", | ||
| "required": true, | ||
| "validate": { | ||
| "type": "Integer", | ||
| "minimum": 1, | ||
| "maximum": 10 | ||
| }, | ||
| "default": 5 | ||
| }, | ||
| { | ||
| "name": "test", | ||
| "required": true, | ||
| "validate": { | ||
| "type": "Fieldname" | ||
| } | ||
| }, | ||
| { | ||
| "name": "percent", | ||
| "validate": { | ||
| "type": "Float", | ||
| "minimum": "85.5" | ||
| } | ||
|
|
||
| } | ||
| ] | ||
|
|
||
| ``` | ||
|
|
||
| ## Example | ||
|
|
||
| ``` json | ||
| { | ||
| "meta": {...} | ||
| "customSearchCommand": [ | ||
| { | ||
| "commandName": "testcommand", | ||
| "fileName": "commandlogic.py", | ||
| "commandType": "streaming", | ||
| "requireSeachAssistant": true, | ||
| "version": 2, | ||
| "description": "This is a test command", | ||
| "syntax": "| testcommand fieldname=<Name of field> pattern=<Valid regex pattern>", | ||
| "usage": "public", | ||
| "arguments": [ | ||
| { | ||
| "name": "fieldname", | ||
| "validate": { | ||
| "type": "Fieldname" | ||
| } | ||
| }, | ||
| { | ||
| "name": "pattern", | ||
| "validate": { | ||
| "type": "RegularExpression" | ||
| }, | ||
| "required": true | ||
| } | ||
| ] | ||
| } | ||
| ], | ||
| "pages": {...} | ||
| } | ||
| ``` | ||
|
|
||
| Generated python file named `testcommand.py`: | ||
|
|
||
| ``` python | ||
| import sys | ||
| import import_declare_test | ||
|
|
||
| from splunklib.searchcommands import \ | ||
| dispatch, StreamingCommand, Configuration, Option, validators | ||
| from commandlogic import stream | ||
|
|
||
| @Configuration() | ||
| class testcommandCommand(StreamingCommand): | ||
| """ | ||
|
|
||
| ##Syntax | ||
| This is a test command | ||
|
|
||
| ##Description | ||
| | testcommand fieldname=<Name of field> pattern=<Valid regex pattern> | ||
|
|
||
| """ | ||
|
|
||
| fieldname = Option(name = "fieldname",require = False, validate = validators.Fieldname(), default = "") | ||
| pattern = Option(name = "pattern",require = True, validate = validators.RegularExpression(), default = "") | ||
|
|
||
|
|
||
| def stream(self, events): | ||
| # Put your event transformation code here | ||
| return stream(self,events) | ||
|
|
||
| dispatch(testcommandCommand, sys.argv, sys.stdin, sys.stdout, __name__) | ||
| ``` | ||
|
|
||
| Generated stanza in `commands.conf` file | ||
|
|
||
| ``` | ||
| [testcommand] | ||
| filename = testcommand.py | ||
| chunked = true | ||
| python.version = python3 | ||
| ``` | ||
|
|
||
| Generated stanza in `searchbnf.conf` file | ||
|
|
||
| ``` | ||
| [testcommand] | ||
| syntax = | testcommand fieldname=<Name of field> pattern=<Valid regex pattern> | ||
| description = This is a test command. | ||
| usage = public | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,7 +24,6 @@ | |
| import colorama as c | ||
| import fnmatch | ||
| import filecmp | ||
|
|
||
| from splunk_add_on_ucc_framework import ( | ||
| __version__, | ||
| exceptions, | ||
|
|
@@ -508,6 +507,44 @@ def generate( | |
| logger.info( | ||
| f"Installed add-on requirements into {ucc_lib_target} from {source}" | ||
| ) | ||
| if global_config.has_custom_search_commands(): | ||
artemrys marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| for command in global_config.custom_search_commands: | ||
| file_path = os.path.join(source, "bin", command["fileName"]) | ||
| if not os.path.isfile(file_path): | ||
| logger.error( | ||
| f"{command['fileName']} is not present in `{os.path.join(source, 'bin')}` directory. " | ||
| "Please ensure the file exists." | ||
| ) | ||
| sys.exit(1) | ||
|
|
||
| if (command["requireSeachAssistant"] is False) and ( | ||
| command.get("description") | ||
| or command.get("usage") | ||
| or command.get("syntax") | ||
| ): | ||
|
||
| logger.warning( | ||
| "requireSeachAssistant is set to false " | ||
| "but atrributes required for 'searchbnf.conf' is defined which is not required." | ||
artemrys marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ) | ||
| if (command["requireSeachAssistant"] is True) and not ( | ||
| command.get("description") | ||
| and command.get("usage") | ||
| and command.get("syntax") | ||
| ): | ||
| logger.error( | ||
| "One of the attributes among `description`, `usage`, `syntax` " | ||
| " is not been defined in globalConfig. Defined them as requireSeachAssistant is set to True. " | ||
| ) | ||
| sys.exit(1) | ||
| if command["version"] == 1: | ||
|
||
| command["fileName"] = command["fileName"].replace(".py", "") | ||
| if command["commandName"] != command["fileName"]: | ||
| logger.error( | ||
| f"Filename: {command['fileName']} and CommandName: {command['commandName']}" | ||
| " should be same for version 1 of custom search command." | ||
| ) | ||
| sys.exit(1) | ||
|
|
||
| generated_files.extend( | ||
| begin( | ||
| global_config=global_config, | ||
|
|
@@ -518,6 +555,7 @@ def generate( | |
| app_manifest=app_manifest, | ||
| addon_version=addon_version, | ||
| has_ui=global_config.meta.get("isVisible", True), | ||
| custom_search_commands=global_config.custom_search_commands, | ||
| ) | ||
| ) | ||
| # TODO: all FILES GENERATED object: generated_files, use it for comparison | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # | ||
| # Copyright 2025 Splunk Inc. | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
| # | ||
| from typing import Any, Dict, Union | ||
|
|
||
| from splunk_add_on_ucc_framework.generators.conf_files import ConfGenerator | ||
|
|
||
|
|
||
| class CommandsConf(ConfGenerator): | ||
| __description__ = ( | ||
| "Generates `commands.conf` for custom commands provided in the globalConfig." | ||
| ) | ||
|
|
||
| def _set_attributes(self, **kwargs: Any) -> None: | ||
| self.conf_file = "commands.conf" | ||
| if self._global_config and self._global_config.has_custom_search_commands(): | ||
| self.command_names = [] | ||
| for command in kwargs["custom_search_commands"]: | ||
| self.command_names.append(command["commandName"]) | ||
|
|
||
| def generate_conf(self) -> Union[Dict[str, str], None]: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens if there is a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the |
||
| if not ( | ||
| self._global_config and self._global_config.has_custom_search_commands() | ||
| ): | ||
| return None | ||
|
|
||
| file_path = self.get_file_output_path(["default", self.conf_file]) | ||
| self.set_template_and_render( | ||
| template_file_path=["conf_files"], file_name="commands.conf.template" | ||
| ) | ||
| rendered_content = self._template.render( | ||
| command_names=self.command_names, | ||
| ) | ||
| self.writer( | ||
| file_name=self.conf_file, | ||
| file_path=file_path, | ||
| content=rendered_content, | ||
| ) | ||
| return {self.conf_file: file_path} | ||
Uh oh!
There was an error while loading. Please reload this page.