Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
84c9d33
feat: custom seach command feature
hetangmodi-crest Jan 20, 2025
b5989f6
feat: template files for custom search command feature
hetangmodi-crest Jan 20, 2025
039fe0d
tests(unit): add unit test cases
hetangmodi-crest Jan 20, 2025
7432330
tests(smoke): add files for smoke test case
hetangmodi-crest Jan 20, 2025
3802847
Merge branch 'develop' into feat/custom-search-command
hetangmodi-crest Jan 20, 2025
fda2c7f
doc: update docs, fix lint
hetangmodi-crest Jan 23, 2025
85be57e
ci: fix app_inspect failure
hetangmodi-crest Jan 23, 2025
d73930c
test(smoke): update smoke tests, add better handling
hetangmodi-crest Jan 24, 2025
d4e3f3e
chore: resolve merge conflict
hetangmodi-crest Jan 24, 2025
181da69
Merge branch 'develop' into feat/custom-search-command
hetangmodi-crest Jan 29, 2025
2e288e7
fix: update schema.json
hetangmodi-crest Feb 4, 2025
d90a475
Merge branch 'develop' into feat/custom-search-command
hetangmodi-crest Feb 4, 2025
f8d6ce3
chore: merge branch 'develop' into feat/custom-search-command
hetangmodi-crest Feb 7, 2025
1425f9b
feat: generate files using FileGenerator class
hetangmodi-crest Feb 13, 2025
1ef0399
tests: add unit and smoke test cases
hetangmodi-crest Feb 13, 2025
b68d9a6
doc: update documentation
hetangmodi-crest Feb 13, 2025
3951671
Merge branch 'develop' into feat/custom-search-command
hetangmodi-crest Feb 13, 2025
b3a42b6
docs: updated docs regarding generated conf, xml and html files
srv-rr-github-token Feb 13, 2025
9af4085
ci: fix pipeline failures
hetangmodi-crest Feb 13, 2025
2866cf9
docs: updated docs regarding generated conf, xml and html files
srv-rr-github-token Feb 13, 2025
312ee3c
Merge branch 'develop' into feat/custom-search-command
hetangmodi-crest Feb 17, 2025
8119835
docs: resolve typos
hetangmodi-crest Feb 19, 2025
afc6bbb
Merge branch 'develop' into feat/custom-search-command
hetangmodi-crest Feb 19, 2025
5f44b3a
Merge branch 'develop' into feat/custom-search-command
hetangmodi-crest Mar 4, 2025
4f0a32f
chore: fix typos in source code
hetangmodi-crest Mar 5, 2025
8823338
ci: fix globalConfig everything
hetangmodi-crest Mar 5, 2025
a7ec116
feat: added check for Splunk built-in commands
hetangmodi-crest Mar 10, 2025
9986ec3
chore: merge branch 'develop' into feat/custom-search-command
hetangmodi-crest Mar 10, 2025
e47b2cd
chore: add license headers
hetangmodi-crest Mar 10, 2025
1adadbb
refactor: removed version 1 support
hetangmodi-crest Mar 13, 2025
ff8da5b
tests: updated unit test cases
hetangmodi-crest Mar 13, 2025
24d9860
Merge branch 'develop' into feat/custom-search-command
vtsvetkov-splunk Mar 14, 2025
b9a73e3
Merge branch 'develop' into feat/custom-search-command
hetangmodi-crest Mar 17, 2025
80d43e6
chore: Merge branch 'develop' into feat/custom-search-command
hetangmodi-crest Mar 17, 2025
d9c8229
chore: merge branch 'develop' into feat/custom-search-command
hetangmodi-crest Mar 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
212 changes: 212 additions & 0 deletions docs/custom_search_commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
# 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.


## 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`. |
| 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",
"requiredSearchAssistant": true,
"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
```
3 changes: 3 additions & 0 deletions docs/generated_files.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ The following table describes the files generated by UCC framework.
| File Name | File Location | File Description |
| ------------ | ------------ | ----------------- |
| app.conf | output/&lt;YOUR_ADDON_NAME&gt;/default | Generates `app.conf` with the details mentioned in globalConfig[meta] |
| commands.conf | output/&lt;YOUR_ADDON_NAME&gt;/default | Generates `commands.conf` for custom commands provided in the globalConfig. |
| searchbnf.conf | output/&lt;YOUR_ADDON_NAME&gt;/default | Generates `searchbnf.conf` for custom search commands provided in the globalConfig. |
| inputs.conf | output/&lt;YOUR_ADDON_NAME&gt;/default | Generates `inputs.conf` and `inputs.conf.spec` file for the services mentioned in globalConfig |
| server.conf | output/&lt;YOUR_ADDON_NAME&gt;/default | Generates `server.conf` for the custom conf files created as per configurations in globalConfig |
| restmap.conf | output/&lt;YOUR_ADDON_NAME&gt;/default | Generates `restmap.conf` for the custom REST handlers that are generated based on configs from globalConfig |
Expand All @@ -22,4 +24,5 @@ The following table describes the files generated by UCC framework.
| inputs.xml | output/&lt;YOUR_ADDON_NAME&gt;/default/data/ui/views | Generates inputs.xml based on inputs configuration present in globalConfig, in `default/data/ui/views/inputs.xml` folder |
| _redirect.xml | output/&lt;YOUR_ADDON_NAME&gt;/default/data/ui/views | Generates ta_name_redirect.xml file, if oauth is mentioned in globalConfig, in `default/data/ui/views/` folder. |
| _.html | output/&lt;YOUR_ADDON_NAME&gt;/default/data/ui/alerts | Generates `alert_name.html` file based on alerts configuration present in globalConfig, in `default/data/ui/alerts` folder. |
| _.py | output/&lt;YOUR_ADDON_NAME&gt;/bin | Generates Python files for custom commands provided in the globalConfig. |

1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ nav:
- Modify fields On change: "entity/modifyFieldsOnValue.md"
- Help property: "entity/help_message.md"

- Custom search commands: "custom_search_commands.md"
- Table: "table.md"
- Additional packaging: "additional_packaging.md"
- UCC ignore: "uccignore.md"
Expand Down
48 changes: 47 additions & 1 deletion splunk_add_on_ucc_framework/commands/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import colorama as c
import fnmatch
import filecmp

from splunk_add_on_ucc_framework import (
__version__,
exceptions,
Expand Down Expand Up @@ -52,6 +51,7 @@
)
from splunk_add_on_ucc_framework.generators.file_generator import begin
from splunk_add_on_ucc_framework.generators.conf_files.create_app_conf import AppConf
from splunk_add_on_ucc_framework.const import SPLUNK_COMMANDS


logger = logging.getLogger("ucc_gen")
Expand Down Expand Up @@ -508,6 +508,51 @@ def generate(
logger.info(
f"Installed add-on requirements into {ucc_lib_target} from {source}"
)
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["requiredSearchAssistant"] is False) and (
command.get("description")
or command.get("usage")
or command.get("syntax")
):
logger.warning(
"requiredSearchAssistant is set to false "
"but attributes required for 'searchbnf.conf' is defined which is not required."
)
if (command["requiredSearchAssistant"] 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. Define them as requiredSearchAssistant is set to True. "
)
sys.exit(1)
command["fileName"] = command["fileName"].replace(".py", "")

if command["commandName"] in SPLUNK_COMMANDS:
logger.error(
f"CommandName: {command['commandName']}"
" cannot have the same name as Splunk built-in command."
)
sys.exit(1)
if command["commandName"] == command["fileName"]:
# Here we are generating file based on commandName therefore
# the core logic should not have the same name as commandName
logger.error(
f"Filename: {command['fileName']} and CommandName: {command['commandName']}"
" should not be same for custom search command."
)
sys.exit(1)

generated_files.extend(
begin(
global_config=global_config,
Expand All @@ -518,6 +563,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
Expand Down
Loading
Loading