Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
91 changes: 84 additions & 7 deletions docs/custom_search_commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,15 @@ python.version = python3
| arguments<span class="required-asterisk">\*</span> | array[objects] | 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 |
| description | string or array[string] | Provide description of the custom search command. To increase the readability of a comprehensive description in json, it is possible to split it in an array of strings. |
| shortdesc | string | A one sentence description of the search command, used for searchbnf.conf |
| syntax | string | Syntax for custom search commands will be automatically generated based on the command name and the parameters. If the syntax attribute is specified, the provided string is used instead. |
| tags | string | One or more words that users might type into the search bar which are similar to the command name. |
| examples | array[objects] | List of example search strings, used for searchbnf.conf |

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`. For more information about these attributes please refer to the [searchbnf.conf docs](https://docs.splunk.com/Documentation/Splunk/9.4.2/Admin/Searchbnfconf)
If `requiredSearchAssistant` is set to True, `description`, and `usage` attributes are mandatory, as they are essential for generating `searchbnf.conf`. The command syntax is automatically derived from the command specification. For more information about these attributes please refer to the [searchbnf.conf docs](https://docs.splunk.com/Documentation/Splunk/9.4.2/Admin/Searchbnfconf)

**NOTE:**
The user-defined Python file must include specific functions based on the command type:
Expand All @@ -83,9 +86,11 @@ If `requiredSearchAssistant` is set to True, the `syntax`, `description`, and `u
| name<span class="required-asterisk">\*</span> | string | Name of the argument |
| defaultValue | string/number | Default value of the argument. |
| required | boolean | 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`. |
| validate | object | Specify validation for the argument. It can be any of `Integer`, `Float`, `Boolean`, `RegularExpression`, `FieldName`, `Set`, `Match`, `List`, `Map`, `Duration`. |
| syntax | string | Syntax for arguments is automatically generated based on the validation. If the syntax attribute for an argument is specified, the syntax value is used for the parameter value instead. The syntax string must only specify the value not the argument name. |
| syntaxGeneration | boolean | Specifies if the parameter should be added to the syntax. If `syntaxGeneration` is false, the parameter is omitted. Default: true. |

UCC currently supports five types of validations provided by `splunklib` library:
UCC currently supports some types of validations provided by `splunklib` library:

- IntegerValidator
+ you can optionally define `minimum` and `maximum` properties.
Expand All @@ -95,10 +100,26 @@ UCC currently supports five types of validations provided by `splunklib` library
+ no additional properties required.
- RegularExpressionValidator
+ no additional properties required.
+ validates if the argument value is a valid regex expression.
- FieldnameValidator
+ no additional properties required.
- SetValidator
+ the property `values` is required, which is a list of allowed strings.
+ validates if the values list contains the argument value.
- MatchValidator
+ the properties `name` and `pattern` is required, where the name is only used for error messages and the pattern must be a valid regex pattern.
+ validates of the argument value matches the specified regex expression.
- ListValidator
+ no additional properties required.
+ validates if the argument value is a valid list and passes the parsed list to the property.
- MapValidator
+ the property `map` is required, where the map must be a dictionary of key value pairs where the key must be a string and the value must either be a string, a number or a boolean.
+ validates if the argument matches a key of the dictionary and passes the corresponding value to the property.
- DurationValidator
+ no additional properties required.

For more information, refer [splunklib API docs](https://splunk-python-sdk.readthedocs.io/en/latest/searchcommands.html)

For more information, refer [splunklib API docs](https://splunk-python-sdk.readthedocs.io/en/latest/searchcommands.html) or [validators.py source](https://github.com/splunk/splunk-sdk-python/blob/develop/splunklib/searchcommands/validators.py).

For example:

Expand Down Expand Up @@ -126,11 +147,61 @@ For example:
"validate": {
"type": "Float",
"minimum": "85.5"
},
"syntaxGeneration": false
},
{
"name": "animals",
"validate": {
"type": "Set",
"values": [
"cat",
"dog",
"wombat"
]
}
},
{
"name": "last",
"validate": {
"type": "Match",
"name": "Day duration",
"pattern": "^[0-9]+(d|m|y)?$"
},
"syntax": "<int>(d|m|y)?"
},
{
"name": "urgency",
"validate": {
"type": "Map",
"map": {
"high": 3,
"medium": 2,
"low": 1
}
}

}
]
```

## Examples (for search command usage)

| Property | Type | Description |
| ------------------------------------------------ | ------ | ------------------------------------------------ |
| search<span class="required-asterisk">\*</span> | string | Example search command |
| comment<span class="required-asterisk">\*</span> | string | Provide description of the example search string |

Each search command can have multiple examples, which are shown displayed in the search assistant. The Compact mode, only shows the first example. In the Full mode, the top three examples are displayed.

For example:

```json
"examples": [
{
"search": "generatetextcommand count=5 text=\"Hallo There\"",
"comment": "Generates 5 \"Hallo There\" events enumerated starting by 1"
}
]
```

## Example
Expand Down Expand Up @@ -161,6 +232,12 @@ For example:
"name": "text",
"required": true
}
],
"examples": [
{
"search": "generatetextcommand count=5 text=\"Hallo There\"",
"comment": "Generates 5 \"Hallo There\" events enumerated starting by 1"
}
]
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,66 @@ def __init__(
if global_config.has_custom_search_commands():
for command in global_config.custom_search_commands:
if command.get("requiredSearchAssistant", False):
if "syntax" in command:
syntax = command["syntax"]
else:
params_syntax = []
for param in command["arguments"]:
if param.get("syntaxGeneration", True):
validator = param.get("validate", {}).get("type", None)
if "syntax" in param:
param_syntax = f"{param['name']}={param['syntax']}"
elif validator and validator in (
"Set",
"Integer",
"Float",
"Boolean",
"List",
"Duration",
"Map",
):
if validator in ("Integer", "Float", "Duration"):
param_syntax = f"{param['name']}=<int>"
if validator == "Boolean":
param_syntax = f"{param['name']}=<bool>"
if validator == "Set":
param_syntax = f"{param['name']}=({'|'.join(param['validate']['values'])})"
if validator == "List":
param_syntax = (
f"{param['name']}=<string>(,<string>)*"
)
if validator == "Map":
param_syntax = f"{param['name']}=({'|'.join(param['validate']['map'].keys())})"
else:
param_syntax = f"{param['name']}=<string>"
if param.get("required", False):
params_syntax.append(param_syntax)
else:
params_syntax.append(f"({param_syntax})?")

syntax = f"{command['commandName']} {' '.join(params_syntax)}"
if len(syntax) > 120:
syntax = syntax.split(" ")
syntax_lines = [syntax[0]]
for part in syntax[1:]:
if len(syntax_lines[-1]) < 100:
syntax_lines[-1] += f" {part}"
else:
syntax_lines.append(part)
syntax = " \\\n".join(syntax_lines)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The join separator " \\\n" produces backslash line continuations in the .conf file. Worth verifying Splunk's conf parser handles backslash continuations correctly in description values of searchbnf.conf. If not, joining with a single space might be safer.


description = command["description"]
if isinstance(description, list):
description = " \\\n".join(description)

searchbnf_dict = {
"command_name": command["commandName"],
"description": command["description"],
"syntax": command["syntax"],
"description": description,
"shortdesc": command.get("shortdesc", None),
"syntax": syntax,
"usage": command["usage"],
"tags": command.get("tags", None),
"examples": command.get("examples", []),
}
self.searchbnf_info.append(searchbnf_dict)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,16 @@ def __init__(
"default": argument.get("defaultValue"),
}
self.argument_generator(argument_list, argument_dict)

description = command.get("description")
if description and isinstance(description, list):
description = "\n ".join(description)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The join separator "\n " (newline + 4 spaces) assumes the docstring context always has 4-space indentation. Verify this is consistent with the template's indentation.

self.commands_info.append(
{
"imported_file_name": imported_file_name,
"file_name": command["commandName"],
"class_name": command["commandName"].title(),
"description": command.get("description"),
"description": description,
"syntax": command.get("syntax"),
"template": template,
"list_arg": argument_list,
Expand Down Expand Up @@ -92,6 +96,18 @@ def argument_generator(
if args
else f", validate=validators.{validate_type}()"
)
elif validate_type == "Set":
allowed_values = validate.get("values")
validate_str = (
f", validate=validators.Set({str(allowed_values).strip('[]')})"
)
elif validate_type == "Map":
option_map = validate.get("map")
validate_str = f", validate=validators.Map(**{str(option_map)})"
elif validate_type == "Match":
name = validate.get("name")
pattern = validate.get("pattern")
validate_str = f", validate=validators.Match('{name}', '{pattern}')"
else:
validate_str = f", validate=validators.{validate_type}()"

Expand All @@ -108,6 +124,7 @@ def argument_generator(
f"{validate_str}, "
f"default='{arg.get('default', '')}')"
)

argument_list.append(arg_str)
return argument_list

Expand Down
7 changes: 4 additions & 3 deletions splunk_add_on_ucc_framework/global_config_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -730,17 +730,18 @@ def _validate_custom_search_commands(self) -> None:

if (command.get("requiredSearchAssistant", False) is False) and (
command.get("description")
or command.get("shortdesc")
or command.get("usage")
or command.get("syntax")
or command.get("tags")
or command.get("examples")
):
logger.warning(
"requiredSearchAssistant is set to false "
"but attributes required for 'searchbnf.conf' is defined which is not required."
)
if (command.get("requiredSearchAssistant", False) is True) and not (
command.get("description")
and command.get("usage")
and command.get("syntax")
command.get("description") and command.get("usage")
):
raise GlobalConfigValidatorException(
"One of the attributes among `description`, `usage`, `syntax`"
Expand Down
Loading