Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
2 changes: 1 addition & 1 deletion splunk_add_on_ucc_framework/commands/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ def generate(
global_config_update.handle_global_config_update(global_config, gc_path)
try:
validator = global_config_validator.GlobalConfigValidator(
internal_root_dir, global_config
internal_root_dir, global_config, source
)
validator.validate()
logger.info("globalConfig file is valid")
Expand Down
175 changes: 175 additions & 0 deletions splunk_add_on_ucc_framework/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
#
# 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.
#

# TODO: Update the list as and when Splunk introduces new commands.
# Links to use: https://docs.splunk.com/Documentation/SplunkCloud/latest/SearchReference
# https://docs.splunk.com/Documentation/Splunk/latest/SearchReference
SPLUNK_COMMANDS = [
"abstract",
"accum",
"addcoltotals",
"addinfo",
"addtotals",
"analyzefields",
"anomalies",
"anomalousvalue",
"anomalydetection",
"append",
"appendcols",
"appendpipe",
"arules",
"associate",
"autoregress",
"awssnsalert",
"bin",
"bucket",
"bucketdir",
"chart",
"cluster",
"cofilter",
"collect",
"concurrency",
"contingency",
"convert",
"correlate",
"ctable",
"datamodel",
"datamodelsimple",
"dbinspect",
"dbxquery",
"dedup",
"delete",
"delta",
"diff",
"entitymerge",
"erex",
"eval",
"eventcount",
"eventstats",
"extract",
"fieldformat",
"fields",
"fieldsummary",
"filldown",
"fillnull",
"findtypes",
"folderize",
"foreach",
"format",
"from",
"fromjson",
"gauge",
"gentimes",
"geom",
"geomfilter",
"geostats",
"head",
"highlight",
"history",
"iconify",
"inputcsv",
"inputintelligence",
"inputlookup",
"iplocation",
"join",
"kmeans",
"kvform",
"loadjob",
"localize",
"localop",
"lookup",
"makecontinuous",
"makemv",
"makeresults",
"map",
"mcollect",
"metadata",
"metasearch",
"meventcollect",
"mpreview",
"msearch",
"mstats",
"multikv",
"multisearch",
"mvcombine",
"mvexpand",
"nomv",
"outlier",
"outputcsv",
"outputlookup",
"outputtext",
"overlap",
"pivot",
"predict",
"rangemap",
"rare",
"regex",
"reltime",
"rename",
"replace",
"require",
"rest",
"return",
"reverse",
"rex",
"rtorder",
"run",
"savedsearch",
"script",
"scrub",
"search",
"searchtxn",
"selfjoin",
"sendalert",
"sendemail",
"set",
"setfields",
"sichart",
"sirare",
"sistats",
"sitimechart",
"sitop",
"sort",
"spath",
"stats",
"strcat",
"streamstats",
"table",
"tags",
"tail",
"timechart",
"timewrap",
"tojson",
"top",
"transaction",
"transpose",
"trendline",
"tscollect",
"tstats",
"typeahead",
"typelearner",
"typer",
"union",
"uniq",
"untable",
"walklex",
"where",
"x11",
"xmlkv",
"xmlunescape",
"xpath",
"xyseries",
]
7 changes: 7 additions & 0 deletions splunk_add_on_ucc_framework/global_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,10 @@ def configs(self) -> List[Any]:
def alerts(self) -> List[Dict[str, Any]]:
return self._content.get("alerts", [])

@property
def custom_search_commands(self) -> List[Dict[str, Any]]:
return self._content.get("customSearchCommand", [])

@property
def meta(self) -> Dict[str, Any]:
return self._content["meta"]
Expand Down Expand Up @@ -271,6 +275,9 @@ def has_configuration(self) -> bool:
def has_alerts(self) -> bool:
return bool(self.alerts)

def has_custom_search_commands(self) -> bool:
return bool(self.custom_search_commands)

def has_dashboard(self) -> bool:
return self.dashboard is not None

Expand Down
57 changes: 54 additions & 3 deletions splunk_add_on_ucc_framework/global_config_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from typing import Any, Dict, List
import logging
import itertools
from splunk_add_on_ucc_framework.const import SPLUNK_COMMANDS

import jsonschema

Expand Down Expand Up @@ -47,8 +48,14 @@ class GlobalConfigValidator:
Custom validation should be implemented here.
"""

def __init__(self, source_dir: str, global_config: global_config_lib.GlobalConfig):
self._source_dir = source_dir
def __init__(
self,
internal_root_dir: str,
global_config: global_config_lib.GlobalConfig,
source: str = "",
):
self._internal_root_dir = internal_root_dir
self._source_dir = source
self._global_config = global_config
self._config = global_config.content
self.resolved_configuration = global_config.resolved_configuration
Expand All @@ -58,7 +65,7 @@ def _validate_config_against_schema(self) -> None:
Validates config against JSON schema.
Raises jsonschema.ValidationError if config is not valid.
"""
schema_path = os.path.join(self._source_dir, "schema", "schema.json")
schema_path = os.path.join(self._internal_root_dir, "schema", "schema.json")
Copy link
Member

Choose a reason for hiding this comment

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

Why this change?

Copy link
Contributor Author

@hetangmodi-crest hetangmodi-crest Apr 17, 2025

Choose a reason for hiding this comment

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

The "source_dir" seemed an ambiguous variable name - does source_dir point to source code of add-on that is being built or UCC code source, hence, I have renamed the _source_dir to _internal_root_dir, otherwise there is no functionality change.

with open(schema_path, encoding="utf-8") as f_schema:
schema_raw = f_schema.read()
schema = json.loads(schema_raw)
Expand Down Expand Up @@ -710,6 +717,49 @@ def _validate_meta_default_view(self) -> None:
'meta.defaultView == "dashboard" but there is no dashboard defined in globalConfig'
)

def _validate_custom_search_commands(self) -> None:
for command in self._global_config.custom_search_commands:
file_path = os.path.join(self._source_dir, "bin", command["fileName"])
if not os.path.isfile(file_path):
raise GlobalConfigValidatorException(
f"{command['fileName']} is not present in `{os.path.join(self._source_dir, 'bin')}` directory. "
"Please ensure the file exists."
)

if (command.get("requiredSearchAssistant", False) 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.get("requiredSearchAssistant", False) is True) and not (
command.get("description")
and command.get("usage")
and command.get("syntax")
):
raise GlobalConfigValidatorException(
"One of the attributes among `description`, `usage`, `syntax`"
" is not been defined in globalConfig. Define them as requiredSearchAssistant is set to True."
)

if command["commandName"] in SPLUNK_COMMANDS:
raise GlobalConfigValidatorException(
f"CommandName: {command['commandName']}"
" cannot have the same name as Splunk built-in command."
)

fileName_without_extension = command["fileName"].replace(".py", "")
if command["commandName"] == fileName_without_extension:
# Here we are generating file based on commandName therefore
# the core logic should not have the same name as commandName
raise GlobalConfigValidatorException(
f"Filename: {fileName_without_extension} and CommandName: {command['commandName']}"
" should not be same for custom search command."
)

def validate(self) -> None:
self._validate_config_against_schema()
if self._global_config.has_pages():
Expand All @@ -723,6 +773,7 @@ def validate(self) -> None:
self._validate_checkbox_group()
self._validate_groups()
self._validate_field_modifications()
self._validate_custom_search_commands()
self._validate_alerts()
self._validate_meta_default_view()

Expand Down
Loading
Loading