Skip to content

Commit 0800d46

Browse files
committed
feat: added support for the validators Set, Match, List, Map and Duration for custom command arguments.
1 parent 05e1535 commit 0800d46

File tree

6 files changed

+325
-4
lines changed

6 files changed

+325
-4
lines changed

docs/custom_search_commands.md

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@ If `requiredSearchAssistant` is set to True, the `syntax`, `description`, and `u
8686
| name<span class="required-asterisk">\*</span> | string | Name of the argument |
8787
| defaultValue | string/number | Default value of the argument. |
8888
| required | boolean | Specify if the argument is required or not. |
89-
| validate | object | Specify validation for the argument. It can be any of `Integer`, `Float`, `Boolean`, `RegularExpression` or `FieldName`. |
89+
| validate | object | Specify validation for the argument. It can be any of `Integer`, `Float`, `Boolean`, `RegularExpression`, `FieldName`, `Set`, `Match`, `List`, `Map`, `Duration`. |
9090

91-
UCC currently supports five types of validations provided by `splunklib` library:
91+
UCC currently supports some types of validations provided by `splunklib` library:
9292

9393
- IntegerValidator
9494
+ you can optionally define `minimum` and `maximum` properties.
@@ -98,10 +98,29 @@ UCC currently supports five types of validations provided by `splunklib` library
9898
+ no additional properties required.
9999
- RegularExpressionValidator
100100
+ no additional properties required.
101+
+ validates if the argument value is a valid regex expression.
101102
- FieldnameValidator
102103
+ no additional properties required.
104+
- SetValidator
105+
- the property `values` is required, which is a list of allowed strings.
106+
- validates if the values list contains the argument value.
107+
- MatchValidator
108+
- 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.
109+
- validates of the argument value matches the specified regex expression.
103110

104-
For more information, refer [splunklib API docs](https://splunk-python-sdk.readthedocs.io/en/latest/searchcommands.html)
111+
- ListValidator
112+
- no additional properties required.
113+
- validates if the argument value is a valid list and passes the parsed list to the property.
114+
115+
- MapValidator
116+
- 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.
117+
- validates if the argument matches a key of the dictionary and passes the corresponding value to the property.
118+
119+
- DurationValidator
120+
- no additional properties required.
121+
122+
123+
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).
105124

106125
For example:
107126

@@ -131,9 +150,38 @@ For example:
131150
"minimum": "85.5"
132151
}
133152

153+
},
154+
{
155+
"name": "animals",
156+
"validate": {
157+
"type": "Set",
158+
"values": [
159+
"cat",
160+
"dog",
161+
"wombat"
162+
]
163+
}
164+
},
165+
{
166+
"name": "name",
167+
"validate": {
168+
"type": "Match",
169+
"name": "Name pattern",
170+
"pattern": "^[A-Z][a-z]+$"
171+
}
172+
},
173+
{
174+
"name": "urgency",
175+
"validate": {
176+
"type": "Map",
177+
"map": {
178+
"high": 3,
179+
"medium": 2,
180+
"low": 1
181+
}
182+
}
134183
}
135184
]
136-
137185
```
138186

139187
## Examples (for search command usage)

splunk_add_on_ucc_framework/generators/python_files/create_custom_command_python.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,18 @@ def argument_generator(
9292
if args
9393
else f", validate=validators.{validate_type}()"
9494
)
95+
elif validate_type == "Set":
96+
allowed_values = validate.get("values")
97+
validate_str = (
98+
f", validate=validators.Set({str(allowed_values).strip('[]')})"
99+
)
100+
elif validate_type == "Map":
101+
option_map = validate.get("map")
102+
validate_str = f", validate=validators.Map(**{str(option_map)})"
103+
elif validate_type == "Match":
104+
name = validate.get("name")
105+
pattern = validate.get("pattern")
106+
validate_str = f", validate=validators.Match('{name}', '{pattern}')"
95107
else:
96108
validate_str = f", validate=validators.{validate_type}()"
97109

@@ -108,6 +120,7 @@ def argument_generator(
108120
f"{validate_str}, "
109121
f"default='{arg.get('default', '')}')"
110122
)
123+
111124
argument_list.append(arg_str)
112125
return argument_list
113126

splunk_add_on_ucc_framework/schema/schema.json

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,21 @@
578578
},
579579
{
580580
"$ref": "#/definitions/CustomBooleanValidator"
581+
},
582+
{
583+
"$ref": "#/definitions/CustomSetValidator"
584+
},
585+
{
586+
"$ref": "#/definitions/CustomMatchValidator"
587+
},
588+
{
589+
"$ref": "#/definitions/CustomListValidator"
590+
},
591+
{
592+
"$ref": "#/definitions/CustomMapValidator"
593+
},
594+
{
595+
"$ref": "#/definitions/CustomDurationValidator"
581596
}
582597
]
583598
},
@@ -667,6 +682,111 @@
667682
],
668683
"additionalProperties": false
669684
},
685+
"CustomSetValidator": {
686+
"type": "object",
687+
"properties": {
688+
"type": {
689+
"const": "Set",
690+
"type": "string",
691+
"description": "Validates value against a set of allowed values."
692+
},
693+
"values": {
694+
"type": "array",
695+
"items": {
696+
"type": "string"
697+
},
698+
"description": "List of allowed values."
699+
}
700+
},
701+
"required": [
702+
"type",
703+
"values"
704+
],
705+
"additionalProperties": false
706+
},
707+
"CustomMatchValidator": {
708+
"type": "object",
709+
"properties": {
710+
"type": {
711+
"const": "Match",
712+
"type": "string",
713+
"description": "Validates option values by regex pattern"
714+
},
715+
"name": {
716+
"type": "string",
717+
"description": "Name for the pattern, which is used for the error message."
718+
},
719+
"pattern": {
720+
"type": "string",
721+
"description": "Regular expression pattern to validate against."
722+
}
723+
},
724+
"required": [
725+
"type",
726+
"pattern"
727+
],
728+
"additionalProperties": false
729+
},
730+
"CustomListValidator": {
731+
"type": "object",
732+
"properties": {
733+
"type": {
734+
"const": "List",
735+
"type": "string",
736+
"description": "Validates a list of strings."
737+
}
738+
},
739+
"required": [
740+
"type"
741+
],
742+
"additionalProperties": false
743+
},
744+
"CustomMapValidator": {
745+
"type": "object",
746+
"properties": {
747+
"type": {
748+
"const": "Map",
749+
"type": "string",
750+
"description": "Validates map option values where the value must be a valid key which is replaced by the value.."
751+
},
752+
"map": {
753+
"type": "object",
754+
"description": "Map which is used to validate and translate option values.",
755+
"additionalProperties": {
756+
"oneOf": [
757+
{
758+
"type": "string"
759+
},
760+
{
761+
"type": "number"
762+
},
763+
{
764+
"type": "boolean"
765+
}
766+
]
767+
}
768+
}
769+
},
770+
"required": [
771+
"type",
772+
"map"
773+
],
774+
"additionalProperties": false
775+
},
776+
"CustomDurationValidator": {
777+
"type": "object",
778+
"properties": {
779+
"type": {
780+
"const": "Duration",
781+
"type": "string",
782+
"description": "Validates duration option values."
783+
}
784+
},
785+
"required": [
786+
"type"
787+
],
788+
"additionalProperties": false
789+
},
670790
"ConfigurationPage": {
671791
"type": "object",
672792
"properties": {

tests/unit/generators/python_files/test_create_custom_command_python.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,63 @@ def transforming_custom_search_command():
6666
]
6767

6868

69+
@pytest.fixture
70+
def custom_search_command_validators():
71+
return [
72+
{
73+
"commandName": "testcommand",
74+
"commandType": "generating",
75+
"fileName": "test.py",
76+
"requiredSearchAssistant": True,
77+
"description": "This is test command",
78+
"syntax": "testcommand count=<event_count> text=<string>",
79+
"usage": "public",
80+
"arguments": [
81+
{
82+
"name": "count",
83+
"required": True,
84+
"validate": {"type": "Integer", "minimum": 5, "maximum": 10},
85+
},
86+
{
87+
"name": "max_word",
88+
"validate": {"type": "Integer", "maximum": 100},
89+
},
90+
{
91+
"name": "age",
92+
"validate": {"type": "Integer", "minimum": 18},
93+
},
94+
{"name": "text", "required": True, "defaultValue": "test_text"},
95+
{"name": "contains"},
96+
{"name": "fieldname", "validate": {"type": "Fieldname"}},
97+
{
98+
"name": "animals",
99+
"validate": {"type": "Set", "values": ["cat", "dog", "wombat"]},
100+
},
101+
{
102+
"name": "name",
103+
"validate": {
104+
"type": "Match",
105+
"name": "Name pattern",
106+
"pattern": "^[A-Z][a-z]+$",
107+
},
108+
},
109+
{
110+
"name": "urgency",
111+
"validate": {
112+
"type": "Map",
113+
"map": {"high": 3, "medium": 2.2, "low": "one"},
114+
},
115+
},
116+
{
117+
"name": "volume",
118+
"validate": {"type": "Float", "minimum": 2.2, "maximum": 197.45},
119+
"required": True,
120+
},
121+
],
122+
}
123+
]
124+
125+
69126
def test_for_transforming_command_with_error(
70127
transforming_custom_search_command,
71128
global_config_all_json,
@@ -147,6 +204,37 @@ def test_for_transforming_command_without_map(
147204
]
148205

149206

207+
def test_for_search_command_validators(
208+
global_config_all_json,
209+
input_dir,
210+
output_dir,
211+
custom_search_command_validators,
212+
):
213+
global_config_all_json._content["customSearchCommand"] = (
214+
custom_search_command_validators
215+
)
216+
custom_command_py = CustomCommandPy(
217+
global_config_all_json,
218+
input_dir,
219+
output_dir,
220+
)
221+
222+
assert custom_command_py.commands_info[0]["list_arg"] == [
223+
"count = Option(name='count', require=True, "
224+
"validate=validators.Integer(minimum=5, maximum=10))",
225+
"max_word = Option(name='max_word', require=False, validate=validators.Integer(maximum=100))",
226+
"age = Option(name='age', require=False, validate=validators.Integer(minimum=18))",
227+
"text = Option(name='text', require=True, default='test_text')",
228+
"contains = Option(name='contains', require=False)",
229+
"fieldname = Option(name='fieldname', require=False, validate=validators.Fieldname())",
230+
"animals = Option(name='animals', require=False, validate=validators.Set('cat', 'dog', 'wombat'))",
231+
"name = Option(name='name', require=False, validate=validators.Match('Name pattern', '^[A-Z][a-z]+$'))",
232+
"urgency = Option(name='urgency', require=False, "
233+
"validate=validators.Map(**{'high': 3, 'medium': 2.2, 'low': 'one'}))",
234+
"volume = Option(name='volume', require=True, validate=validators.Float(minimum=2.2, maximum=197.45))",
235+
]
236+
237+
150238
def test_init_without_custom_command(
151239
global_config_only_configuration,
152240
input_dir,
@@ -240,6 +328,9 @@ class GeneratetextcommandCommand(GeneratingCommand):
240328
"""
241329
count = Option(name='count', require=True, validate=validators.Integer(minimum=5, maximum=10))
242330
text = Option(name='text', require=True)
331+
animals = Option(name='animals', require=False, validate=validators.Set('cat', 'dog', 'wombat'))
332+
name = Option(name='name', require=False, validate=validators.Match('Name pattern', '^[A-Z][a-z]+$'))
333+
urgency = Option(name='urgency', require=False, validate=validators.Map(**{'high': 3, 'medium': 2.2, 'low': 'one'}))
243334
244335
def generate(self):
245336
return generate(self)

tests/unit/test_global_config.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,25 @@ def test_global_config_custom_search_commands(global_config_all_json):
8787
"validate": {"type": "Integer", "minimum": 5, "maximum": 10},
8888
},
8989
{"name": "text", "required": True},
90+
{
91+
"name": "animals",
92+
"validate": {"type": "Set", "values": ["cat", "dog", "wombat"]},
93+
},
94+
{
95+
"name": "name",
96+
"validate": {
97+
"type": "Match",
98+
"name": "Name pattern",
99+
"pattern": "^[A-Z][a-z]+$",
100+
},
101+
},
102+
{
103+
"name": "urgency",
104+
"validate": {
105+
"type": "Map",
106+
"map": {"high": 3, "medium": 2.2, "low": "one"},
107+
},
108+
},
90109
],
91110
"examples": [
92111
{

0 commit comments

Comments
 (0)