From bc6a36bd323a31ac2284f0a621caa2bfaa4c93ac Mon Sep 17 00:00:00 2001 From: Hetang Modi <62056057+hetangmodi-crest@users.noreply.github.com> Date: Fri, 13 Sep 2024 14:56:59 +0530 Subject: [PATCH] feat: restructure conf and spec file generation (#1328) **Issue number:** ADDON-73368 ## Summary ### Changes > As per the new design, we are restructuring the code of generating `.conf` and `.conf.spec` files that are supported by UCC. One specific change for `inputs.conf.spec`, we are now writing the default value and description of a field in the spec file. ### User experience > One change for `inputs.conf.spec`, we are now writing the description and default value of a field in the spec file if they are mentioned in the globalConfig, apart from it there are no changes from user end, all the files that were generated before will be generated as is with these changes. ## Checklist If your change doesn't seem to apply, please leave them unchecked. * [x] I have performed a self-review of this change * [x] Changes have been tested * [ ] Changes are documented * [x] PR title follows [conventional commit semantics](https://www.conventionalcommits.org/en/v1.0.0/) --------- Co-authored-by: srv-rr-github-token <94607705+srv-rr-github-token@users.noreply.github.com> --- docs/alert_actions/index.md | 2 +- docs/generated_files.md | 10 + splunk_add_on_ucc_framework/app_conf.py | 75 ----- splunk_add_on_ucc_framework/commands/build.py | 74 +---- .../alert_actions_conf_gen.py | 274 ------------------ .../alert_actions_helper.py | 23 +- .../modular_alert_builder/arf_consts.py | 2 - .../commands/modular_alert_builder/builder.py | 9 - .../commands/rest_builder/builder.py | 40 --- .../global_config_builder_schema.py | 2 +- .../generators/conf_files/__init__.py | 40 +++ .../generators/conf_files/conf_generator.py | 47 +++ .../conf_files/create_account_conf.py | 59 ++++ .../conf_files/create_alert_actions_conf.py | 161 ++++++++++ .../generators/conf_files/create_app_conf.py | 77 +++++ .../conf_files/create_eventtypes_conf.py | 53 ++++ .../conf_files/create_inputs_conf.py | 90 ++++++ .../conf_files/create_restmap_conf.py | 52 ++++ .../conf_files/create_server_conf.py | 53 ++++ .../conf_files/create_settings_conf.py | 77 +++++ .../generators/conf_files/create_tags_conf.py | 52 ++++ .../generators/conf_files/create_web_conf.py | 48 +++ .../generators/file_const.py | 30 ++ splunk_add_on_ucc_framework/rest_map_conf.py | 54 ---- .../schema/schema.json | 1 + splunk_add_on_ucc_framework/server_conf.py | 36 --- .../README/account_conf_spec.template | 4 + .../README/alert_actions_conf_spec.template} | 3 +- .../README/inputs_conf_spec.template | 6 + .../README/settings_conf_spec.template | 4 + .../conf_files/alert_actions_conf.template} | 6 +- .../templates/conf_files/app_conf.template | 31 ++ .../conf_files/eventtypes_conf.template} | 2 +- .../templates/conf_files/inputs_conf.template | 4 + .../conf_files/restmap_conf.template | 12 + .../templates/conf_files/server_conf.template | 4 + .../conf_files/settings_conf.template | 1 + .../conf_files/tags_conf.template} | 0 .../templates/conf_files/web_conf.template | 9 + .../html_templates}/textarea.html | 0 splunk_add_on_ucc_framework/web_conf.py | 52 ---- .../splunk_ta_uccexample_account.conf.spec | 20 +- .../splunk_ta_uccexample_settings.conf.spec | 14 +- .../Splunk_TA_UCCExample/default/app.conf | 3 +- .../Splunk_TA_UCCExample/default/restmap.conf | 3 +- .../Splunk_TA_UCCExample/default/server.conf | 2 +- .../Splunk_TA_UCCExample/default/web.conf | 3 +- .../README/alert_actions.conf.spec | 3 +- .../README/inputs.conf.spec | 54 ++-- .../splunk_ta_uccexample_account.conf.spec | 30 +- .../splunk_ta_uccexample_settings.conf.spec | 14 +- .../Splunk_TA_UCCExample/bin/test_alert.py | 4 + .../default/alert_actions.conf | 11 +- .../Splunk_TA_UCCExample/default/app.conf | 3 +- .../default/data/ui/alerts/test_alert.html | 10 + .../default/eventtypes.conf | 2 +- .../Splunk_TA_UCCExample/default/inputs.conf | 3 +- .../Splunk_TA_UCCExample/default/restmap.conf | 5 +- .../Splunk_TA_UCCExample/default/server.conf | 2 +- .../Splunk_TA_UCCExample/default/tags.conf | 2 +- .../Splunk_TA_UCCExample/default/web.conf | 3 +- .../globalConfig.json | 8 + .../test_alert_actions_conf_gen.py | 122 -------- .../modular_alert_builder/test_builder.py | 26 -- .../conf_files/test_conf_files_init.py | 36 +++ .../conf_files/test_conf_generator.py | 143 +++++++++ .../test_create_account_conf_spec.py | 152 ++++++++++ .../test_create_alert_actions_conf.py | 212 ++++++++++++++ .../conf_files/test_create_app_conf.py | 243 ++++++++++++++++ .../conf_files/test_create_eventypes_conf.py | 93 ++++++ .../conf_files/test_create_inputs_conf.py | 222 ++++++++++++++ .../conf_files/test_create_restmap_conf.py | 92 ++++++ .../conf_files/test_create_server_conf.py | 119 ++++++++ .../conf_files/test_create_settings_conf.py | 211 ++++++++++++++ .../conf_files/test_create_tags_conf.py | 92 ++++++ .../conf_files/test_create_web_conf.py | 89 ++++++ .../test_create_alert_actions_html.py | 8 +- tests/unit/test_app_conf.py | 141 --------- tests/unit/test_restmap_conf.py | 49 ---- tests/unit/test_server_conf.py | 24 -- tests/unit/test_utils.py | 13 + tests/unit/test_web_conf.py | 47 --- 82 files changed, 2791 insertions(+), 1121 deletions(-) delete mode 100644 splunk_add_on_ucc_framework/app_conf.py delete mode 100644 splunk_add_on_ucc_framework/commands/modular_alert_builder/alert_actions_conf_gen.py create mode 100644 splunk_add_on_ucc_framework/generators/conf_files/__init__.py create mode 100644 splunk_add_on_ucc_framework/generators/conf_files/conf_generator.py create mode 100644 splunk_add_on_ucc_framework/generators/conf_files/create_account_conf.py create mode 100644 splunk_add_on_ucc_framework/generators/conf_files/create_alert_actions_conf.py create mode 100644 splunk_add_on_ucc_framework/generators/conf_files/create_app_conf.py create mode 100644 splunk_add_on_ucc_framework/generators/conf_files/create_eventtypes_conf.py create mode 100644 splunk_add_on_ucc_framework/generators/conf_files/create_inputs_conf.py create mode 100644 splunk_add_on_ucc_framework/generators/conf_files/create_restmap_conf.py create mode 100644 splunk_add_on_ucc_framework/generators/conf_files/create_server_conf.py create mode 100644 splunk_add_on_ucc_framework/generators/conf_files/create_settings_conf.py create mode 100644 splunk_add_on_ucc_framework/generators/conf_files/create_tags_conf.py create mode 100644 splunk_add_on_ucc_framework/generators/conf_files/create_web_conf.py delete mode 100644 splunk_add_on_ucc_framework/rest_map_conf.py delete mode 100644 splunk_add_on_ucc_framework/server_conf.py create mode 100644 splunk_add_on_ucc_framework/templates/README/account_conf_spec.template rename splunk_add_on_ucc_framework/{commands/modular_alert_builder/arf_template/alert_actions.conf.spec.template => templates/README/alert_actions_conf_spec.template} (89%) create mode 100644 splunk_add_on_ucc_framework/templates/README/inputs_conf_spec.template create mode 100644 splunk_add_on_ucc_framework/templates/README/settings_conf_spec.template rename splunk_add_on_ucc_framework/{commands/modular_alert_builder/arf_template/alert_actions.conf.template => templates/conf_files/alert_actions_conf.template} (61%) create mode 100644 splunk_add_on_ucc_framework/templates/conf_files/app_conf.template rename splunk_add_on_ucc_framework/{commands/modular_alert_builder/arf_template/eventtypes.conf.template => templates/conf_files/eventtypes_conf.template} (66%) create mode 100644 splunk_add_on_ucc_framework/templates/conf_files/inputs_conf.template create mode 100644 splunk_add_on_ucc_framework/templates/conf_files/restmap_conf.template create mode 100644 splunk_add_on_ucc_framework/templates/conf_files/server_conf.template create mode 100644 splunk_add_on_ucc_framework/templates/conf_files/settings_conf.template rename splunk_add_on_ucc_framework/{commands/modular_alert_builder/arf_template/tags.conf.template => templates/conf_files/tags_conf.template} (100%) create mode 100644 splunk_add_on_ucc_framework/templates/conf_files/web_conf.template rename splunk_add_on_ucc_framework/{commands/modular_alert_builder/arf_template/default_html_theme => templates/html_templates}/textarea.html (100%) delete mode 100644 splunk_add_on_ucc_framework/web_conf.py delete mode 100644 tests/unit/commands/modular_alert_builder/test_alert_actions_conf_gen.py create mode 100644 tests/unit/generators/conf_files/test_conf_files_init.py create mode 100644 tests/unit/generators/conf_files/test_conf_generator.py create mode 100644 tests/unit/generators/conf_files/test_create_account_conf_spec.py create mode 100644 tests/unit/generators/conf_files/test_create_alert_actions_conf.py create mode 100644 tests/unit/generators/conf_files/test_create_app_conf.py create mode 100644 tests/unit/generators/conf_files/test_create_eventypes_conf.py create mode 100644 tests/unit/generators/conf_files/test_create_inputs_conf.py create mode 100644 tests/unit/generators/conf_files/test_create_restmap_conf.py create mode 100644 tests/unit/generators/conf_files/test_create_server_conf.py create mode 100644 tests/unit/generators/conf_files/test_create_settings_conf.py create mode 100644 tests/unit/generators/conf_files/test_create_tags_conf.py create mode 100644 tests/unit/generators/conf_files/test_create_web_conf.py delete mode 100644 tests/unit/test_app_conf.py delete mode 100644 tests/unit/test_restmap_conf.py delete mode 100644 tests/unit/test_server_conf.py delete mode 100644 tests/unit/test_web_conf.py diff --git a/docs/alert_actions/index.md b/docs/alert_actions/index.md index 00b7b9c3e..7716b4487 100644 --- a/docs/alert_actions/index.md +++ b/docs/alert_actions/index.md @@ -25,7 +25,7 @@ Developers are required to add alerts in the global config file to create an Ale | Property | Type | Description | |---------------------------------------------------------------------------|--------|--------------------------------------------------------------------------------------------------------| -| type\* | string | The type of the user input in the alert. Available choices: "text", "checkbox", "singleSelect", "radio", "singleSelectSplunkSearch". | +| type\* | string | The type of the user input in the alert. Available choices: "text", "textarea", "checkbox", "singleSelect", "radio", "singleSelectSplunkSearch". | | label\* | string | The text that would be shown in the alert action UI. | | field\* | string | The field that would be used in the scripts to get the value from the user input. These are defined as `param.` in the `alert_actions.conf`. | | options | array | Static choices that a user can select in the alert action UI. | diff --git a/docs/generated_files.md b/docs/generated_files.md index 1d7e0a2c6..8f6e3801b 100644 --- a/docs/generated_files.md +++ b/docs/generated_files.md @@ -8,6 +8,16 @@ Below table describes the files generated by UCC framework | File Name | File Location | File Description | | ------------ | ------------ | ----------------- | +| app.conf | output/<YOUR_ADDON_NAME>/default | Generates `app.conf` with the details mentioned in globalConfig[meta] | +| inputs.conf | output/<YOUR_ADDON_NAME>/default | Generates `inputs.conf` and `inputs.conf.spec` file for the services mentioned in globalConfig | +| server.conf | output/<YOUR_ADDON_NAME>/default | Generates `server.conf` for the custom conf files created as per configurations in globalConfig | +| restmap.conf | output/<YOUR_ADDON_NAME>/default | Generates `restmap.conf` for the custom REST handlers that are generated based on configs from globalConfig | +| web.conf | output/<YOUR_ADDON_NAME>/default | Generates `web.conf` to expose the endpoints generated in `restmap.conf` which is generated based on configurations from globalConfig. | +| alert_actions.conf | output/<YOUR_ADDON_NAME>/default | Generates `alert_actions.conf` and `alert_actions.conf.spec` file for the custom alert actions defined in globalConfig | +| eventtypes.conf | output/<YOUR_ADDON_NAME>/default | Generates `eventtypes.conf` file if the sourcetype is mentioned in Adaptive Response of custom alert action in globalConfig | +| tags.conf | output/<YOUR_ADDON_NAME>/default | Generates `tags.conf` file based on the `eventtypes.conf` created for custom alert actions. | +| _account.conf | output/<YOUR_ADDON_NAME>/README | Generates `_account.conf.spec` file for the configuration mentioned in globalConfig | +| _settings.conf | output/<YOUR_ADDON_NAME>/README | Generates `_settings.conf.spec` file for the Proxy, Logging or Custom Tab mentioned in globalConfig | | configuration.xml | output/<YOUR_ADDON_NAME>/default/data/ui/views | Generates configuration.xml file in `default/data/ui/views/` folder if globalConfig is present. | | dashboard.xml | output/<YOUR_ADDON_NAME>/default/data/ui/views | Generates dashboard.xml file based on dashboard configuration present in globalConfig in `default/data/ui/views` folder. | | default.xml | output/<YOUR_ADDON_NAME>/default/data/ui/nav | Generates default.xml file based on configs present in globalConfigin in `default/data/ui/nav` folder. | diff --git a/splunk_add_on_ucc_framework/app_conf.py b/splunk_add_on_ucc_framework/app_conf.py deleted file mode 100644 index 5aff1e130..000000000 --- a/splunk_add_on_ucc_framework/app_conf.py +++ /dev/null @@ -1,75 +0,0 @@ -# -# Copyright 2024 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. -# -import time -from typing import Sequence - -import addonfactory_splunk_conf_parser_lib as conf_parser -from splunk_add_on_ucc_framework import app_manifest as app_manifest_lib - -APP_CONF_FILE_NAME = "app.conf" - - -class AppConf: - def __init__(self) -> None: - self._app_conf = conf_parser.TABConfigParser() - - def read(self, path: str) -> None: - self._app_conf.read(path) - - def update( - self, - version: str, - app_manifest: app_manifest_lib.AppManifest, - conf_file_names: Sequence[str], - is_visible: bool, - **kwargs: str, - ) -> None: - if "launcher" not in self._app_conf: - self._app_conf.add_section("launcher") - if "id" not in self._app_conf: - self._app_conf.add_section("id") - if "install" not in self._app_conf: - self._app_conf.add_section("install") - if "package" not in self._app_conf: - self._app_conf.add_section("package") - if "ui" not in self._app_conf: - self._app_conf.add_section("ui") - if "triggers" not in self._app_conf and conf_file_names: - self._app_conf.add_section("triggers") - - self._app_conf["launcher"]["version"] = version - self._app_conf["launcher"]["description"] = app_manifest.get_description() - authors = app_manifest.get_authors() - first_author = authors[0] - self._app_conf["launcher"]["author"] = first_author["name"] - self._app_conf["id"]["version"] = version - self._app_conf["id"]["name"] = app_manifest.get_addon_name() - self._app_conf["install"]["build"] = str(int(time.time())) - self._app_conf["install"]["is_configured"] = "false" - self._app_conf["install"]["state"] = "enabled" - self._app_conf["package"]["id"] = app_manifest.get_addon_name() - self._app_conf["package"]["check_for_updates"] = kwargs["check_for_updates"] - - self._app_conf["ui"]["label"] = app_manifest.get_title() - if kwargs.get("supported_themes") != "": - self._app_conf["ui"]["supported_themes"] = kwargs["supported_themes"] - self._app_conf["ui"]["is_visible"] = "true" if is_visible else "false" - for conf_file_name in conf_file_names: - self._app_conf["triggers"][f"reload.{conf_file_name}"] = "simple" - - def write(self, path: str) -> None: - with open(path, "w") as fd: - self._app_conf.write(fd) diff --git a/splunk_add_on_ucc_framework/commands/build.py b/splunk_add_on_ucc_framework/commands/build.py index 224ce04e5..d1c2ccadc 100644 --- a/splunk_add_on_ucc_framework/commands/build.py +++ b/splunk_add_on_ucc_framework/commands/build.py @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # -import configparser import glob import json import logging @@ -34,9 +33,7 @@ utils, ) from splunk_add_on_ucc_framework import dashboard -from splunk_add_on_ucc_framework import app_conf as app_conf_lib from splunk_add_on_ucc_framework import meta_conf as meta_conf_lib -from splunk_add_on_ucc_framework import server_conf as server_conf_lib from splunk_add_on_ucc_framework import app_manifest as app_manifest_lib from splunk_add_on_ucc_framework import global_config as global_config_lib from splunk_add_on_ucc_framework.commands.modular_alert_builder import ( @@ -54,6 +51,7 @@ ucc_to_oas, ) 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 logger = logging.getLogger("ucc_gen") @@ -160,19 +158,6 @@ def _add_modular_input( with open(helper_filename, "w") as helper_file: helper_file.write(content) - input_default = os.path.join(outputdir, ta_name, "default", "inputs.conf") - config = configparser.ConfigParser() - if os.path.exists(input_default): - config.read(input_default) - - if config.has_section(input_name): - config[input_name]["python.version"] = "python3" - else: - config[input_name] = {"python.version": "python3"} - - with open(input_default, "w") as configfile: - config.write(configfile) - def _get_ignore_list( addon_name: str, ucc_ignore_path: str, output_directory: str @@ -454,6 +439,7 @@ def generate( gc_path = _get_and_check_global_config_path(source, config_path) if gc_path: + ui_available = True logger.info(f"Using globalConfig file located @ {gc_path}") global_config = global_config_lib.GlobalConfig(gc_path) # handle the update of globalConfig before validating @@ -536,31 +522,13 @@ def generate( _add_modular_input(ta_name, global_config, output_directory) if global_config.has_alerts(): logger.info("Generating alerts code") - alert_builder.generate_alerts( - global_config, ta_name, internal_root_dir, output_directory - ) + alert_builder.generate_alerts(global_config, ta_name, output_directory) conf_file_names = [] conf_file_names.extend(list(scheme.settings_conf_file_names)) conf_file_names.extend(list(scheme.configs_conf_file_names)) conf_file_names.extend(list(scheme.oauth_conf_file_names)) - source_server_conf_path = os.path.join(source, "default", "server.conf") - # For now, only create server.conf only if no server.conf is present in - # the source package. - if not os.path.isfile(source_server_conf_path): - server_conf = server_conf_lib.ServerConf() - server_conf.create_default(conf_file_names) - output_server_conf_path = os.path.join( - output_directory, - ta_name, - "default", - server_conf_lib.SERVER_CONF_FILE_NAME, - ) - server_conf.write(output_server_conf_path) - logger.info( - f"Created default {server_conf_lib.SERVER_CONF_FILE_NAME} file in the output folder" - ) if global_config.has_dashboard(): logger.info("Including dashboard") dashboard_definition_json_path = os.path.join( @@ -632,31 +600,17 @@ def generate( f"Updated {app_manifest_lib.APP_MANIFEST_FILE_NAME} file in the output folder" ) - app_conf = app_conf_lib.AppConf() - output_app_conf_path = os.path.join( - output_directory, ta_name, "default", app_conf_lib.APP_CONF_FILE_NAME - ) - app_conf.read(output_app_conf_path) - should_be_visible = False - check_for_updates = "true" - supported_themes = "" - if global_config: - should_be_visible = True - if global_config.meta.get("checkForUpdates") is False: - check_for_updates = "false" - if global_config.meta.get("supportedThemes") is not None: - supported_themes = ", ".join(global_config.meta["supportedThemes"]) - app_conf.update( - addon_version, - app_manifest, - conf_file_names, - should_be_visible, - check_for_updates=check_for_updates, - supported_themes=supported_themes, - ) - app_conf.write(output_app_conf_path) - logger.info(f"Updated {app_conf_lib.APP_CONF_FILE_NAME} file in the output folder") - + # NOTE: merging source and generated 'app.conf' as per previous design + AppConf( + global_config=global_config, + input_dir=source, + output_dir=output_directory, + ucc_dir=internal_root_dir, + addon_name=ta_name, + app_manifest=app_manifest, + addon_version=addon_version, + has_ui=ui_available, + ).generate() license_dir = os.path.abspath(os.path.join(source, os.pardir, "LICENSES")) if os.path.exists(license_dir): logger.info("Copy LICENSES directory") diff --git a/splunk_add_on_ucc_framework/commands/modular_alert_builder/alert_actions_conf_gen.py b/splunk_add_on_ucc_framework/commands/modular_alert_builder/alert_actions_conf_gen.py deleted file mode 100644 index 790b66c6f..000000000 --- a/splunk_add_on_ucc_framework/commands/modular_alert_builder/alert_actions_conf_gen.py +++ /dev/null @@ -1,274 +0,0 @@ -# -# Copyright 2024 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. -# -import json -import logging -from os import linesep, makedirs, path as op -import shutil -from typing import Dict, Any - -from jinja2 import Environment, FileSystemLoader - -from splunk_add_on_ucc_framework.commands.modular_alert_builder import ( - arf_consts as ac, -) -from splunk_add_on_ucc_framework.commands.modular_alert_builder.alert_actions_helper import ( - write_file, -) -from splunk_add_on_ucc_framework.commands.modular_alert_builder.alert_actions_merge import ( - remove_alert_from_conf_file, -) - -logger = logging.getLogger("ucc_gen") - - -class AlertActionsConfGeneration: - def __init__( - self, - input_setting: Dict[str, Any], - package_path: str, - internal_source_path: str, - ) -> None: - self._alert_conf_name = "alert_actions.conf" - self._alert_spec_name = "alert_actions.conf.spec" - self._eventtypes_conf = "eventtypes.conf" - self._tags_conf = "tags.conf" - self._alert_settings = input_setting[ac.MODULAR_ALERTS] - self._package_path = package_path - self._internal_source_path = internal_source_path - # nosemgrep: splunk.autoescape-disabled, python.jinja2.security.audit.autoescape-disabled.autoescape-disabled - self._templates = Environment( - loader=FileSystemLoader( - op.join(op.dirname(op.realpath(__file__)), "arf_template") - ), - trim_blocks=True, - lstrip_blocks=True, - keep_trailing_newline=True, - ) - self._html_fields = [ac.PARAMETERS] - self._default_conf_settings = { - "python.version": "python3", - "is_custom": 1, - "payload_format": "json", - "icon_path": "alerticon.png", - } - - def get_local_conf_file_path(self, conf_name: str) -> str: - local_path = op.join(self._package_path, "default") - if not op.exists(local_path): - makedirs(local_path) - - return op.join(local_path, conf_name) - - def get_spec_file_path(self) -> str: - readme_path = op.join(self._package_path, "README") - if not op.exists(readme_path): - makedirs(readme_path) - return op.join(readme_path, self._alert_spec_name) - - def generate_conf(self) -> None: - logger.info( - 'status="starting", operation="generate", ' - + 'object="alert_actions.conf", object_type="file"' - ) - template = self._templates.get_template("alert_actions.conf.template") - deny_list = frozenset( - [ - "short_name", - "alert_props", - "parameters", - "uuid", - "code", - "largeIcon", - "smallIcon", - "index", - "iconFileName", # it is a config from globalConfig that gets written to icon_path - "customScript", # it is a config from globalConfig only for Python script - ] - ) - alerts: Dict[str, Any] = {} - for alert in self._alert_settings: - alert_name = alert["short_name"] - alerts[alert_name] = [] - for k, v in alert.items(): - if k == "adaptive_response": - new_cam = { - sub_k: sub_v - for sub_k, sub_v in list(v.items()) - if sub_k != "sourcetype" and sub_v - } - value = f"param._cam = {json.dumps(new_cam)}" - alerts[alert_name].append(value) - elif k == "alert_props": - if alert.get("iconFileName", "alerticon.png") != "alerticon.png": - alert["alert_props"]["icon_path"] = alert["iconFileName"] - else: - # we copy UCC framework's alerticon.png only when a custom isn't provided - shutil.copy( - op.join( - self._internal_source_path, "static", "alerticon.png" - ), - op.join(self._package_path, "appserver", "static"), - ) - for pk, pv in v.items(): - value = f"{str(pk).strip()} = {str(pv).strip()}" - alerts[alert_name].append(value) - elif k not in deny_list: - value = f"{str(k).strip()} = {str(v).strip()}" - alerts[alert_name].append(value) - for k, v in alert.items(): - if k == "parameters": - for param in v: - param_name = param["name"].strip() - if param.get("default_value") is not None: - param_default_value = str( - param.get("default_value") - ).strip() - alerts[alert_name].append( - f"param.{param_name} = {param_default_value}" - ) - else: - alerts[alert_name].append(f"param.{param_name} = ") - final_string = template.render(alerts=alerts) - text = linesep.join([s.strip() for s in final_string.splitlines()]) - write_file( - self._alert_conf_name, - self.get_local_conf_file_path(self._alert_conf_name), - text, - ) - logger.info( - 'status="success", operation="generate", ' - + 'object="alert_actions.conf", object_type="file"' - ) - - def generate_eventtypes(self) -> None: - logger.info( - 'status="starting", operation="generate", ' - + 'object="eventtypes.conf", object_type="file"' - ) - template = self._templates.get_template("eventtypes.conf.template") - final_string = template.render(mod_alerts=self._alert_settings) - text = linesep.join([s.strip() for s in final_string.splitlines()]) - file_path = self.get_local_conf_file_path(self._eventtypes_conf) - write_file(self._eventtypes_conf, file_path, text) - - # remove the stanza if not checked - for alert in self._alert_settings: - if alert.get("adaptive_response") and alert["adaptive_response"].get( - "sourcetype" - ): - continue - remove_alert_from_conf_file(alert, file_path) - logger.info( - 'status="success", operation="generate", ' - + 'object="eventtypes.conf", object_type="file"' - ) - - def generate_tags(self) -> None: - logger.info( - 'status="starting", operation="generate", ' - + 'object="tags.conf", object_type="file"' - ) - template = self._templates.get_template("tags.conf.template") - final_string = template.render(mod_alerts=self._alert_settings) - text = linesep.join([s.strip() for s in final_string.splitlines()]) - file_path = self.get_local_conf_file_path(self._tags_conf) - write_file(self._tags_conf, file_path, text) - - # remove the stanza if not checked - for alert in self._alert_settings: - if alert.get("adaptive_response") and alert["adaptive_response"].get( - "sourcetype" - ): - continue - remove_alert_from_conf_file(alert, file_path) - logger.info( - 'status="success", operation="generate", ' - + 'object="tags.conf", object_type="file"' - ) - - def generate_spec(self) -> None: - logger.info( - 'status="starting", operation="generate", ' - + 'object="alert_actions.conf.spec", object_type="file"' - ) - template = self._templates.get_template("alert_actions.conf.spec.template") - _router = { - "dropdownlist": "list", - "text": "string", - "textarea": "string", - "checkbox": "bool", - "password": "password", - "dropdownlist_splunk_search": "list", - "radio": "list", - } - alerts: Dict[str, Any] = {} - for alert in self._alert_settings: - alert_name = alert["short_name"] - alerts[alert_name] = [] - for k, v in alert.items(): - if k == "adaptive_response": - alerts[alert_name].append( - "param._cam = Adaptive Response parameters." - ) - elif k == "parameters": - for param in v: - format_type = _router[param["format_type"]] - is_required = ( - "It's a required parameter." - if param.get("required") and param["required"] - else "" - ) - param_default_value = param.get("default_value") - default_value = ( - f"It's default value is {param_default_value}." - if param_default_value - else "" - ) - value = ( - f'param.{param["name"]} = <{format_type}> ' - f'{param["label"]}. {is_required} {default_value}' - ) - alerts[alert_name].append(value) - final_string = template.render(alerts=alerts) - text = linesep.join([s.strip() for s in final_string.splitlines()]) - write_file(self._alert_spec_name, self.get_spec_file_path(), text) - logger.info( - 'status="success", operation="generate", ' - + 'object="alert_actions.conf.spec", object_type="file"' - ) - - def handle(self) -> None: - self.add_default_settings() - self.generate_conf() - self.generate_spec() - self.generate_eventtypes() - self.generate_tags() - - def add_default_settings(self) -> None: - for alert in self._alert_settings: - if ac.ALERT_PROPS not in list(alert.keys()): - alert[ac.ALERT_PROPS] = {} - for k, v in list(self._default_conf_settings.items()): - if k in list(alert[ac.ALERT_PROPS].keys()): - continue - - alert[ac.ALERT_PROPS][k] = v - logger.info( - 'status="success", operation="Add default setting", alert_name="%s", "%s"="%s"', - alert[ac.SHORT_NAME], - k, - v, - ) diff --git a/splunk_add_on_ucc_framework/commands/modular_alert_builder/alert_actions_helper.py b/splunk_add_on_ucc_framework/commands/modular_alert_builder/alert_actions_helper.py index ad1b59669..8b250c4e3 100644 --- a/splunk_add_on_ucc_framework/commands/modular_alert_builder/alert_actions_helper.py +++ b/splunk_add_on_ucc_framework/commands/modular_alert_builder/alert_actions_helper.py @@ -14,8 +14,7 @@ # limitations under the License. # import logging -import os.path as op -from os import makedirs, remove +from os import makedirs, remove, path import addonfactory_splunk_conf_parser_lib as conf_parser @@ -26,11 +25,15 @@ logger = logging.getLogger("ucc_gen") -def write_file(file_name: str, file_path: str, content: str) -> None: +def write_file(file_name: str, file_path: str, content: str, **kwargs: str) -> None: + """ + :param merge_mode: only supported for .conf and .conf.spec files. + """ logger.debug('operation="write", object="%s" object_type="file"', file_path) + merge_mode = kwargs.get("merge_mode", "stanza_overwrite") do_merge = False - if file_name.endswith(".conf") or file_name.endswith("conf.spec"): + if file_name.endswith(".conf") or file_name.endswith(".conf.spec"): do_merge = True else: logger.debug( @@ -39,19 +42,19 @@ def write_file(file_name: str, file_path: str, content: str) -> None: ) new_file = None - if op.exists(file_path) and do_merge: - new_file = op.join(op.dirname(file_path), "new_" + file_name) + if path.exists(file_path) and do_merge: + new_file = path.join(path.dirname(file_path), "new_" + file_name) if new_file: try: with open(new_file, "w+") as fhandler: fhandler.write(content) - merge_conf_file(new_file, file_path) + merge_conf_file(new_file, file_path, merge_mode=merge_mode) finally: - if op.exists(new_file): + if path.exists(new_file): remove(new_file) else: - if not op.exists(op.dirname(file_path)): - makedirs(op.dirname(file_path)) + if not path.exists(path.dirname(file_path)): + makedirs(path.dirname(file_path)) with open(file_path, "w+") as fhandler: fhandler.write(content) if do_merge: diff --git a/splunk_add_on_ucc_framework/commands/modular_alert_builder/arf_consts.py b/splunk_add_on_ucc_framework/commands/modular_alert_builder/arf_consts.py index cf23aaa7f..5ab91027e 100644 --- a/splunk_add_on_ucc_framework/commands/modular_alert_builder/arf_consts.py +++ b/splunk_add_on_ucc_framework/commands/modular_alert_builder/arf_consts.py @@ -15,6 +15,4 @@ # SHORT_NAME = "short_name" -PARAMETERS = "parameters" MODULAR_ALERTS = "modular_alerts" -ALERT_PROPS = "alert_props" diff --git a/splunk_add_on_ucc_framework/commands/modular_alert_builder/builder.py b/splunk_add_on_ucc_framework/commands/modular_alert_builder/builder.py index 7dfbd20cb..4bf09d4c1 100644 --- a/splunk_add_on_ucc_framework/commands/modular_alert_builder/builder.py +++ b/splunk_add_on_ucc_framework/commands/modular_alert_builder/builder.py @@ -18,7 +18,6 @@ from splunk_add_on_ucc_framework import global_config as global_config_lib from splunk_add_on_ucc_framework.commands.modular_alert_builder import ( - alert_actions_conf_gen, normalize, ) from splunk_add_on_ucc_framework.commands.modular_alert_builder import ( @@ -31,7 +30,6 @@ def generate_alerts( global_config: global_config_lib.GlobalConfig, addon_name: str, - internal_source_dir: str, output_dir: str, ) -> None: envs = normalize.normalize( @@ -41,13 +39,6 @@ def generate_alerts( package_dir = os.path.join(output_dir, addon_name) schema_content = envs["schema.content"] - conf_gen = alert_actions_conf_gen.AlertActionsConfGeneration( - input_setting=schema_content, - package_path=package_dir, - internal_source_path=internal_source_dir, - ) - conf_gen.handle() - py_gen = alert_actions_py_gen.AlertActionsPyGenerator( addon_name=addon_name, input_setting=schema_content, diff --git a/splunk_add_on_ucc_framework/commands/rest_builder/builder.py b/splunk_add_on_ucc_framework/commands/rest_builder/builder.py index d78af7097..eaa32ee8f 100644 --- a/splunk_add_on_ucc_framework/commands/rest_builder/builder.py +++ b/splunk_add_on_ucc_framework/commands/rest_builder/builder.py @@ -20,8 +20,6 @@ from splunk_add_on_ucc_framework.commands.rest_builder import ( global_config_builder_schema, ) -from splunk_add_on_ucc_framework.rest_map_conf import RestmapConf -from splunk_add_on_ucc_framework.web_conf import WebConf from splunk_add_on_ucc_framework.global_config import OSDependentLibraryConfig __all__ = ["RestBuilder"] @@ -142,50 +140,12 @@ def _add_executable_attribute(file_path: str) -> None: def build(self) -> None: for endpoint in self._schema.endpoints: - # If the endpoint is oauth, which is for getting accesstoken. Conf file entries should not get created. - if endpoint._name != "oauth": - if endpoint._name == "settings": - self.output.put( - self.output.default, - f"{endpoint.conf_name}.conf", - endpoint.generate_conf_with_default_values(), - ) - - self.output.put( - self.output.readme, - f"{endpoint.conf_name}.conf.spec", - endpoint.generate_spec(), - ) - - # Add data input of self defined conf to inputs.conf.spec - if endpoint._entities[0] and endpoint._entities[0]._conf_name: - lines = [ - f"[{endpoint._name}://]", - "placeholder = placeholder", - ] - self.output.put( - self.output.readme, "inputs.conf.spec", "\n".join(lines) - ) - self.output.put( self.output.bin, endpoint.rh_name + ".py", endpoint.generate_rh(), ) - self.output.put( - self.output.default, - "restmap.conf", - RestmapConf.build( - self._schema.endpoints, - self._schema.namespace, - ), - ) - self.output.put( - self.output.default, - "web.conf", - WebConf.build(self._schema.endpoints), - ) self.output.put( self.output.bin, "import_declare_test.py", diff --git a/splunk_add_on_ucc_framework/commands/rest_builder/global_config_builder_schema.py b/splunk_add_on_ucc_framework/commands/rest_builder/global_config_builder_schema.py index 74007c3a7..8551d9f5c 100644 --- a/splunk_add_on_ucc_framework/commands/rest_builder/global_config_builder_schema.py +++ b/splunk_add_on_ucc_framework/commands/rest_builder/global_config_builder_schema.py @@ -112,7 +112,7 @@ def _builder_configs(self) -> None: conf_name=config.get("conf"), ) endpoint.add_entity(entity) - # If we have given oauth support then we have to add endpoint for accesstoken + # If we have given oauth support then we have to add endpoint for access_token for entity_element in config["entity"]: if entity_element["type"] == "oauth": log_details = self.global_config.logging_tab diff --git a/splunk_add_on_ucc_framework/generators/conf_files/__init__.py b/splunk_add_on_ucc_framework/generators/conf_files/__init__.py new file mode 100644 index 000000000..07d3fd346 --- /dev/null +++ b/splunk_add_on_ucc_framework/generators/conf_files/__init__.py @@ -0,0 +1,40 @@ +# +# Copyright 2024 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 .conf_generator import ConfGenerator +from .create_alert_actions_conf import AlertActionsConf +from .create_app_conf import AppConf +from .create_eventtypes_conf import EventtypesConf +from .create_inputs_conf import InputsConf +from .create_restmap_conf import RestMapConf +from .create_server_conf import ServerConf +from .create_tags_conf import TagsConf +from .create_web_conf import WebConf +from .create_account_conf import AccountConf +from .create_settings_conf import SettingsConf + +__all__ = [ + "ConfGenerator", + "ServerConf", + "RestMapConf", + "WebConf", + "AlertActionsConf", + "EventtypesConf", + "TagsConf", + "AppConf", + "InputsConf", + "AccountConf", + "SettingsConf", +] diff --git a/splunk_add_on_ucc_framework/generators/conf_files/conf_generator.py b/splunk_add_on_ucc_framework/generators/conf_files/conf_generator.py new file mode 100644 index 000000000..8468cf94d --- /dev/null +++ b/splunk_add_on_ucc_framework/generators/conf_files/conf_generator.py @@ -0,0 +1,47 @@ +# +# Copyright 2024 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, NoReturn +from ..file_generator import FileGenerator + + +class ConfGenerator(FileGenerator): + __description__ = "DESCRIBE THE CONF FILE THAT IS GENERATED" + + def _set_attributes(self, **kwargs: Any) -> Union[NoReturn, None]: + # parse self._global_config and set the require attributes for self + raise NotImplementedError() + + def generate(self) -> Dict[str, str]: + conf_files: Dict[str, str] = {} + conf_file = self.generate_conf() + conf_spec_file = self.generate_conf_spec() + if conf_file: + conf_files.update(conf_file) + if conf_spec_file: + conf_files.update(conf_spec_file) + return conf_files + + def generate_conf(self) -> Union[Dict[str, str], None]: + # logic to pass the configs to template file + # uses the attributes set in _set_attributes method to render the template + # use self.get_file_output_path() to get the output file to create the file + return {"": ""} + + def generate_conf_spec(self) -> Union[Dict[str, str], None]: + # logic to pass the configs to template file + # uses the attributes set in _set_attributes method to render the template + # use self.get_file_output_path() to get the output file to create the file + return {"": ""} diff --git a/splunk_add_on_ucc_framework/generators/conf_files/create_account_conf.py b/splunk_add_on_ucc_framework/generators/conf_files/create_account_conf.py new file mode 100644 index 000000000..05837ffd8 --- /dev/null +++ b/splunk_add_on_ucc_framework/generators/conf_files/create_account_conf.py @@ -0,0 +1,59 @@ +# +# Copyright 2024 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, Tuple, List, Dict, Union + +from splunk_add_on_ucc_framework.generators.conf_files import ConfGenerator + + +class AccountConf(ConfGenerator): + __description__ = ( + "Generates `_account.conf.spec` " + "file for the configuration mentioned in globalConfig" + ) + + def _set_attributes(self, **kwargs: Any) -> None: + self.account_fields: List[Tuple[str, List[str]]] = [] + if self._global_config and self._gc_schema: + self.conf_spec_file = ( + self._global_config.namespace.lower() + "_account.conf.spec" + ) + for account in self._global_config.configs: + # If the endpoint is oauth, which is for getting access_token, conf file entries + # should not get created (compatibility to previous versions) + if account["name"] == "oauth": + continue + content = self._gc_schema._get_oauth_enitities(account["entity"]) + fields = self._gc_schema._parse_fields(content) + self.account_fields.append( + ("", [f"{f._name} = " for f in fields]) + ) + + def generate_conf_spec(self) -> Union[Dict[str, str], None]: + if not self.account_fields: + return None + + file_path = self.get_file_output_path(["README", self.conf_spec_file]) + self.set_template_and_render( + template_file_path=["README"], file_name="account_conf_spec.template" + ) + + rendered_content = self._template.render(account_stanzas=self.account_fields) + self.writer( + file_name=self.conf_spec_file, + file_path=file_path, + content=rendered_content, + ) + return {self.conf_spec_file: file_path} diff --git a/splunk_add_on_ucc_framework/generators/conf_files/create_alert_actions_conf.py b/splunk_add_on_ucc_framework/generators/conf_files/create_alert_actions_conf.py new file mode 100644 index 000000000..de448ab95 --- /dev/null +++ b/splunk_add_on_ucc_framework/generators/conf_files/create_alert_actions_conf.py @@ -0,0 +1,161 @@ +# +# Copyright 2024 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. +# +import json +import shutil +from os import path +from typing import Any, Dict, Union + +from splunk_add_on_ucc_framework.commands.modular_alert_builder import normalize +from splunk_add_on_ucc_framework.generators.conf_files import ConfGenerator + + +class AlertActionsConf(ConfGenerator): + __description__ = ( + "Generates `alert_actions.conf` and `alert_actions.conf.spec` file " + "for the custom alert actions defined in globalConfig" + ) + + def _set_attributes(self, **kwargs: Any) -> None: + self.conf_file = "alert_actions.conf" + self.conf_spec_file = f"{self.conf_file}.spec" + if self._global_config is None: + return + + envs = normalize.normalize( + self._global_config.alerts, + self._global_config.namespace, + ) + schema_content = envs["schema.content"] + self._alert_settings = schema_content["modular_alerts"] + + deny_list = frozenset( + [ + "short_name", + "alert_props", + "parameters", + "uuid", + "code", + "largeIcon", + "smallIcon", + "index", + "iconFileName", # it is a config from globalConfig that gets written to icon_path + "customScript", # it is a config from globalConfig only for Python script + ] + ) + _router = { + "dropdownlist": "list", + "text": "string", + "textarea": "string", + "checkbox": "bool", + "password": "password", + "dropdownlist_splunk_search": "list", + "radio": "list", + } + + self.alerts: Dict[str, Any] = {} + self.alerts_spec: Dict[str, Any] = {} + + for alert in self._alert_settings: + alert_name = alert["short_name"] + self.alerts[alert_name] = [] + self.alerts_spec[alert_name] = [] + # process the 'iconFileName' property for alert actions + if alert.get("iconFileName", "alerticon.png") != "alerticon.png": + self.alerts[alert_name].append(f"icon_path = {alert['iconFileName']}") + else: + self.alerts[alert_name].append("icon_path = alerticon.png") + # we copy UCC framework's alerticon.png only when a custom isn't provided + shutil.copy( + path.join(kwargs["ucc_dir"], "static", "alerticon.png"), + path.join(self._get_output_dir(), "appserver", "static"), + ) + # process alert action properties in bulk + for k, v in alert.items(): + if k == "adaptive_response": + new_cam = { + sub_k: sub_v + for sub_k, sub_v in list(v.items()) + if sub_k != "sourcetype" and sub_v + } + value = f"param._cam = {json.dumps(new_cam)}" + self.alerts[alert_name].append(value) + self.alerts_spec[alert_name].append( + "param._cam = Adaptive Response parameters." + ) + elif k == "parameters": + for param in v: + param_name = param["name"].strip() + if param.get("default_value") is not None: + self.alerts[alert_name].append( + f"param.{param_name} = {str(param['default_value']).strip()}" + ) + else: + self.alerts[alert_name].append(f"param.{param_name} = ") + + # fetching details for alert_actions.conf.spec file + format_type = _router[param["format_type"]] + is_required = ( + "It's a required parameter." + if param.get("required") and param["required"] + else "" + ) + param_default_value = param.get("default_value") + default_value = ( + f"It's default value is {param_default_value}." + if param_default_value + else "" + ) + value = ( + f'param.{param["name"]} = <{format_type}> ' + f'{param["label"]}. {is_required} {default_value}' + ) + self.alerts_spec[alert_name].append(value) + elif k not in deny_list: + value = f"{str(k).strip()} = {str(v).strip()}" + self.alerts[alert_name].append(value) + + def generate_conf(self) -> Union[Dict[str, str], None]: + if not self.alerts: + 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="alert_actions_conf.template" + ) + rendered_content = self._template.render(alerts=self.alerts) + self.writer( + file_name=self.conf_file, + file_path=file_path, + content=rendered_content, + ) + return {self.conf_file: file_path} + + def generate_conf_spec(self) -> Union[Dict[str, str], None]: + if not self.alerts_spec: + return None + + file_path = self.get_file_output_path(["README", self.conf_spec_file]) + self.set_template_and_render( + template_file_path=["README"], + file_name="alert_actions_conf_spec.template", + ) + rendered_content = self._template.render(alerts=self.alerts_spec) + self.writer( + file_name=self.conf_spec_file, + file_path=file_path, + content=rendered_content, + ) + return {self.conf_spec_file: file_path} diff --git a/splunk_add_on_ucc_framework/generators/conf_files/create_app_conf.py b/splunk_add_on_ucc_framework/generators/conf_files/create_app_conf.py new file mode 100644 index 000000000..00aa02fec --- /dev/null +++ b/splunk_add_on_ucc_framework/generators/conf_files/create_app_conf.py @@ -0,0 +1,77 @@ +# +# Copyright 2024 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 time import time +from typing import Any, Dict + +from splunk_add_on_ucc_framework.generators.conf_files import ConfGenerator + + +class AppConf(ConfGenerator): + __description__ = ( + "Generates `app.conf` with the details mentioned in globalConfig[meta]" + ) + + def _set_attributes(self, **kwargs: Any) -> None: + self.conf_file = "app.conf" + self.check_for_updates = "true" + self.custom_conf = [] + self.name = self._addon_name + self.id = self._addon_name + self.supported_themes = "" + + if self._global_config and self._gc_schema: + self.custom_conf.extend(list(self._gc_schema.settings_conf_file_names)) + self.custom_conf.extend(list(self._gc_schema.configs_conf_file_names)) + self.custom_conf.extend(list(self._gc_schema.oauth_conf_file_names)) + + if self._global_config.meta.get("checkForUpdates") is False: + self.check_for_updates = "false" + if self._global_config.meta.get("supportedThemes") is not None: + self.supported_themes = ", ".join( + self._global_config.meta["supportedThemes"] + ) + + self.addon_version = kwargs["addon_version"] + self.is_visible = str(kwargs["has_ui"]).lower() + self.description = kwargs["app_manifest"].get_description() + self.author = kwargs["app_manifest"].get_authors()[0]["name"] + self.build = str(int(time())) + + def generate_conf(self) -> Dict[str, str]: + file_path = self.get_file_output_path(["default", self.conf_file]) + self.set_template_and_render( + template_file_path=["conf_files"], file_name="app_conf.template" + ) + rendered_content = self._template.render( + custom_conf=self.custom_conf, + addon_version=self.addon_version, + check_for_updates=self.check_for_updates, + supported_themes=self.supported_themes, + description=self.description, + author=self.author, + name=self.name, + build=self.build, + id=self.id, + label=self.description, + is_visible=self.is_visible, + ) + self.writer( + file_name=self.conf_file, + file_path=file_path, + content=rendered_content, + merge_mode="item_overwrite", + ) + return {self.conf_file: file_path} diff --git a/splunk_add_on_ucc_framework/generators/conf_files/create_eventtypes_conf.py b/splunk_add_on_ucc_framework/generators/conf_files/create_eventtypes_conf.py new file mode 100644 index 000000000..517468e04 --- /dev/null +++ b/splunk_add_on_ucc_framework/generators/conf_files/create_eventtypes_conf.py @@ -0,0 +1,53 @@ +# +# Copyright 2024 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, List, Union + +from splunk_add_on_ucc_framework.commands.modular_alert_builder import normalize +from splunk_add_on_ucc_framework.generators.conf_files import ConfGenerator + + +class EventtypesConf(ConfGenerator): + __description__ = ( + "Generates `eventtypes.conf` file if the sourcetype is mentioned" + " in Adaptive Response of custom alert action in globalConfig" + ) + + def _set_attributes(self, **kwargs: Any) -> None: + self.conf_file = "eventtypes.conf" + self.alert_settings: Dict[str, List[Dict[str, Any]]] = {} + if self._global_config: + envs = normalize.normalize( + self._global_config.alerts, + self._global_config.namespace, + ) + schema_content = envs["schema.content"] + self.alert_settings = schema_content["modular_alerts"] + + def generate_conf(self) -> Union[Dict[str, str], None]: + if not self.alert_settings: + 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="eventtypes_conf.template" + ) + rendered_content = self._template.render(mod_alerts=self.alert_settings) + self.writer( + file_name=self.conf_file, + file_path=file_path, + content=rendered_content, + ) + return {self.conf_file: file_path} diff --git a/splunk_add_on_ucc_framework/generators/conf_files/create_inputs_conf.py b/splunk_add_on_ucc_framework/generators/conf_files/create_inputs_conf.py new file mode 100644 index 000000000..e5ec4f47e --- /dev/null +++ b/splunk_add_on_ucc_framework/generators/conf_files/create_inputs_conf.py @@ -0,0 +1,90 @@ +# +# Copyright 2024 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, List, Union + +from splunk_add_on_ucc_framework.generators.conf_files import ConfGenerator + + +class InputsConf(ConfGenerator): + __description__ = ( + "Generates `inputs.conf` and `inputs.conf.spec` " + "file for the services mentioned in globalConfig" + ) + + def _set_attributes(self, **kwargs: Any) -> None: + self.conf_file = "inputs.conf" + self.conf_spec_file = f"{self.conf_file}.spec" + self.input_names: List[Dict[str, List[str]]] = [] + if self._global_config: + for service in self._global_config.inputs: + properties = [] + if service.get("conf") is not None: + # Add data input of self defined conf to inputs.conf.spec + self.input_names.append( + {service["name"]: ["placeholder = placeholder"]} + ) + continue + for entity in service.get("entity", {"field": "name"}): + # TODO: add the details and updates on what to skip and process + if entity["field"] == "name": + continue + nl = "\n" # hack for `f-string expression part cannot include a backslash` + # TODO: enhance the message formation for inputs.conf.spec file + properties.append( + f"{entity['field']} = {entity.get('help', '').replace(nl, ' ')} " + f"{'' if entity.get('defaultValue') is None else ' Default: ' + str(entity['defaultValue'])}" + ) + + self.input_names.append({service["name"]: properties}) + + def generate_conf(self) -> Union[Dict[str, str], None]: + if not self.input_names: + return None + + file_path = self.get_file_output_path(["default", self.conf_file]) + stanzas: List[str] = [] + for k in self.input_names: + stanzas.extend(k.keys()) + self.set_template_and_render( + template_file_path=["conf_files"], file_name="inputs_conf.template" + ) + + rendered_content = self._template.render(input_names=stanzas) + self.writer( + file_name=self.conf_file, + file_path=file_path, + content=rendered_content, + ) + return {self.conf_file: file_path} + + def generate_conf_spec(self) -> Union[Dict[str, str], None]: + if not self.input_names: + return None + + file_path = self.get_file_output_path(["README", self.conf_spec_file]) + self.set_template_and_render( + template_file_path=["README"], file_name="inputs_conf_spec.template" + ) + + rendered_content = self._template.render( + input_stanzas=self.input_names, + ) + self.writer( + file_name=self.conf_spec_file, + file_path=file_path, + content=rendered_content, + ) + return {self.conf_spec_file: file_path} diff --git a/splunk_add_on_ucc_framework/generators/conf_files/create_restmap_conf.py b/splunk_add_on_ucc_framework/generators/conf_files/create_restmap_conf.py new file mode 100644 index 000000000..6e0b5ca39 --- /dev/null +++ b/splunk_add_on_ucc_framework/generators/conf_files/create_restmap_conf.py @@ -0,0 +1,52 @@ +# +# Copyright 2024 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 RestMapConf(ConfGenerator): + __description__ = ( + "Generates `restmap.conf` for the custom REST handlers that " + "are generated based on configs from globalConfig" + ) + + def _set_attributes(self, **kwargs: Any) -> None: + self.conf_file = "restmap.conf" + if self._gc_schema: + self.endpoints = self._gc_schema.endpoints + self.endpoint_names = ", ".join(sorted([ep.name for ep in self.endpoints])) + self.namespace = self._gc_schema.namespace + + def generate_conf(self) -> Union[Dict[str, str], None]: + if not self._gc_schema: + 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="restmap_conf.template" + ) + rendered_content = self._template.render( + endpoints=self.endpoints, + endpoint_names=self.endpoint_names, + namespace=self.namespace, + ) + self.writer( + file_name=self.conf_file, + file_path=file_path, + content=rendered_content, + ) + return {self.conf_file: file_path} diff --git a/splunk_add_on_ucc_framework/generators/conf_files/create_server_conf.py b/splunk_add_on_ucc_framework/generators/conf_files/create_server_conf.py new file mode 100644 index 000000000..e49a3c100 --- /dev/null +++ b/splunk_add_on_ucc_framework/generators/conf_files/create_server_conf.py @@ -0,0 +1,53 @@ +# +# Copyright 2024 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 os.path import isfile, join +from typing import Any, Dict, Union +from splunk_add_on_ucc_framework.generators.conf_files import ConfGenerator + + +class ServerConf(ConfGenerator): + __description__ = ( + "Generates `server.conf` for the custom conf " + "files created as per configurations in globalConfig" + ) + + def _set_attributes(self, **kwargs: Any) -> None: + self.conf_file = "server.conf" + self.custom_conf = [] + if self._gc_schema: + self.custom_conf.extend(list(self._gc_schema.settings_conf_file_names)) + self.custom_conf.extend(list(self._gc_schema.configs_conf_file_names)) + self.custom_conf.extend(list(self._gc_schema.oauth_conf_file_names)) + + def generate_conf(self) -> Union[Dict[str, str], None]: + if not self.custom_conf: + return None + + file_path = self.get_file_output_path(["default", self.conf_file]) + # For now, only create server.conf only if + # no server.conf is present in the source package. + if isfile(join(self._input_dir, "default", self.conf_file)): + return {"": ""} + self.set_template_and_render( + template_file_path=["conf_files"], file_name="server_conf.template" + ) + rendered_content = self._template.render(custom_conf=self.custom_conf) + self.writer( + file_name=self.conf_file, + file_path=file_path, + content=rendered_content, + ) + return {self.conf_file: file_path} diff --git a/splunk_add_on_ucc_framework/generators/conf_files/create_settings_conf.py b/splunk_add_on_ucc_framework/generators/conf_files/create_settings_conf.py new file mode 100644 index 000000000..f1c13bb3b --- /dev/null +++ b/splunk_add_on_ucc_framework/generators/conf_files/create_settings_conf.py @@ -0,0 +1,77 @@ +# +# Copyright 2024 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, Tuple, List, Dict, Union + +from splunk_add_on_ucc_framework.generators.conf_files import ConfGenerator + + +class SettingsConf(ConfGenerator): + __description__ = ( + "Generates `_settings.conf.spec` " + "file for the Proxy, Logging or Custom Tab mentioned in globalConfig" + ) + + def _set_attributes(self, **kwargs: Any) -> None: + self.settings_stanzas: List[Tuple[str, List[str]]] = [] + self.default_content: str = "" + + if self._global_config and self._gc_schema: + self.conf_file = self._global_config.namespace.lower() + "_settings.conf" + self.conf_spec_file = f"{self.conf_file}.spec" + for setting in self._global_config.settings: + content = self._gc_schema._get_oauth_enitities(setting["entity"]) + fields = self._gc_schema._parse_fields(content) + self.settings_stanzas.append( + (setting["name"], [f"{f._name} = " for f in fields]) + ) + if self._gc_schema._endpoints.get("settings") is not None: + self.default_content = self._gc_schema._endpoints[ + "settings" + ].generate_conf_with_default_values() + + def generate_conf(self) -> Union[Dict[str, str], None]: + if not self.default_content: + 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="settings_conf.template" + ) + + rendered_content = self._template.render(default_content=self.default_content) + self.writer( + file_name=self.conf_file, + file_path=file_path, + content=rendered_content, + ) + return {self.conf_file: file_path} + + def generate_conf_spec(self) -> Union[Dict[str, str], None]: + if not self.settings_stanzas: + return None + + file_path = self.get_file_output_path(["README", self.conf_spec_file]) + self.set_template_and_render( + template_file_path=["README"], file_name="settings_conf_spec.template" + ) + + rendered_content = self._template.render(settings_stanzas=self.settings_stanzas) + self.writer( + file_name=self.conf_spec_file, + file_path=file_path, + content=rendered_content, + ) + return {self.conf_spec_file: file_path} diff --git a/splunk_add_on_ucc_framework/generators/conf_files/create_tags_conf.py b/splunk_add_on_ucc_framework/generators/conf_files/create_tags_conf.py new file mode 100644 index 000000000..7306c864b --- /dev/null +++ b/splunk_add_on_ucc_framework/generators/conf_files/create_tags_conf.py @@ -0,0 +1,52 @@ +# +# Copyright 2024 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, List, Union +from splunk_add_on_ucc_framework.commands.modular_alert_builder import normalize +from splunk_add_on_ucc_framework.generators.conf_files import ConfGenerator + + +class TagsConf(ConfGenerator): + __description__ = ( + "Generates `tags.conf` file based on the " + "`eventtypes.conf` created for custom alert actions." + ) + + def _set_attributes(self, **kwargs: Any) -> None: + self.conf_file = "tags.conf" + self.alert_settings: Dict[str, List[Dict[str, Any]]] = {} + if self._global_config: + envs = normalize.normalize( + self._global_config.alerts, + self._global_config.namespace, + ) + schema_content = envs["schema.content"] + self.alert_settings = schema_content["modular_alerts"] + + def generate_conf(self) -> Union[Dict[str, str], None]: + if not self.alert_settings: + 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="tags_conf.template" + ) + rendered_content = self._template.render(mod_alerts=self.alert_settings) + self.writer( + file_name=self.conf_file, + file_path=file_path, + content=rendered_content, + ) + return {self.conf_file: file_path} diff --git a/splunk_add_on_ucc_framework/generators/conf_files/create_web_conf.py b/splunk_add_on_ucc_framework/generators/conf_files/create_web_conf.py new file mode 100644 index 000000000..477d0803c --- /dev/null +++ b/splunk_add_on_ucc_framework/generators/conf_files/create_web_conf.py @@ -0,0 +1,48 @@ +# +# Copyright 2024 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 WebConf(ConfGenerator): + __description__ = ( + "Generates `web.conf` to expose the endpoints generated in " + "`restmap.conf` which is generated based on configurations from globalConfig." + ) + + def _set_attributes(self, **kwargs: Any) -> None: + self.conf_file = "web.conf" + if self._gc_schema: + self.endpoints = self._gc_schema.endpoints + + def generate_conf(self) -> Union[Dict[str, str], None]: + if not self._gc_schema: + 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="web_conf.template" + ) + rendered_content = self._template.render( + endpoints=self.endpoints, + ) + self.writer( + file_name=self.conf_file, + file_path=file_path, + content=rendered_content, + ) + return {self.conf_file: file_path} diff --git a/splunk_add_on_ucc_framework/generators/file_const.py b/splunk_add_on_ucc_framework/generators/file_const.py index eac29f61b..b843b915c 100644 --- a/splunk_add_on_ucc_framework/generators/file_const.py +++ b/splunk_add_on_ucc_framework/generators/file_const.py @@ -15,6 +15,7 @@ # from typing import List, NamedTuple, Type, Union from .file_generator import FileGenerator + from splunk_add_on_ucc_framework.generators.xml_files import ( ConfigurationXml, DashboardXml, @@ -23,6 +24,18 @@ RedirectXml, ) from splunk_add_on_ucc_framework.generators.html_files import AlertActionsHtml +from splunk_add_on_ucc_framework.generators.conf_files import ( + AlertActionsConf, + AppConf, + EventtypesConf, + InputsConf, + RestMapConf, + ServerConf, + TagsConf, + WebConf, + AccountConf, + SettingsConf, +) __all__ = ["FileClass", "GEN_FILE_LIST"] @@ -35,6 +48,23 @@ class FileClass(NamedTuple): GEN_FILE_LIST: List[FileClass] = [ + FileClass("app.conf", AppConf, "default", AppConf.__description__), + FileClass("inputs.conf", InputsConf, "default", InputsConf.__description__), + FileClass("server.conf", ServerConf, "default", ServerConf.__description__), + FileClass("restmap.conf", RestMapConf, "default", RestMapConf.__description__), + FileClass("web.conf", WebConf, "default", WebConf.__description__), + FileClass( + "alert_actions.conf", + AlertActionsConf, + "default", + AlertActionsConf.__description__, + ), + FileClass( + "eventtypes.conf", EventtypesConf, "default", EventtypesConf.__description__ + ), + FileClass("tags.conf", TagsConf, "default", TagsConf.__description__), + FileClass("_account.conf", AccountConf, "README", AccountConf.__description__), + FileClass("_settings.conf", SettingsConf, "README", SettingsConf.__description__), FileClass( "configuration.xml", ConfigurationXml, diff --git a/splunk_add_on_ucc_framework/rest_map_conf.py b/splunk_add_on_ucc_framework/rest_map_conf.py deleted file mode 100644 index 00f050421..000000000 --- a/splunk_add_on_ucc_framework/rest_map_conf.py +++ /dev/null @@ -1,54 +0,0 @@ -# -# Copyright 2024 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 Sequence - -from splunk_add_on_ucc_framework.commands.rest_builder.endpoint.base import ( - RestEndpointBuilder, -) - - -class RestmapConf: - _admin_template = """ -[admin:{namespace}] -match = / -members = {endpoints} -""" - - _external_template = """ -[admin_external:{name}] -handlertype = python -python.version = python3 -handlerfile = {rh_name}.py -handleractions = {actions} -handlerpersistentmode = true -""" - - @classmethod - def build(cls, endpoints: Sequence[RestEndpointBuilder], namespace: str) -> str: - externals = [ - cls._admin_template.format( - namespace=namespace, - endpoints=", ".join([ep.name for ep in endpoints]), - ) - ] - for endpoint in endpoints: - external = cls._external_template.format( - name=endpoint.name, - rh_name=endpoint.rh_name, - actions=", ".join(endpoint.actions()), - ) - externals.append(external) - return "".join(externals) diff --git a/splunk_add_on_ucc_framework/schema/schema.json b/splunk_add_on_ucc_framework/schema/schema.json index 8bc755dd9..f59162c7c 100644 --- a/splunk_add_on_ucc_framework/schema/schema.json +++ b/splunk_add_on_ucc_framework/schema/schema.json @@ -19,6 +19,7 @@ "type": "string", "enum": [ "text", + "textarea", "checkbox", "singleSelect", "radio", diff --git a/splunk_add_on_ucc_framework/server_conf.py b/splunk_add_on_ucc_framework/server_conf.py deleted file mode 100644 index bf4c95477..000000000 --- a/splunk_add_on_ucc_framework/server_conf.py +++ /dev/null @@ -1,36 +0,0 @@ -# -# Copyright 2024 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 Sequence - -import addonfactory_splunk_conf_parser_lib as conf_parser - -SERVER_CONF_FILE_NAME = "server.conf" - - -class ServerConf: - def __init__(self) -> None: - self._server_conf = conf_parser.TABConfigParser() - - def create_default(self, conf_file_names: Sequence[str]) -> None: - self._server_conf.add_section("shclustering") - for conf_file_name in conf_file_names: - self._server_conf["shclustering"][ - f"conf_replication_include.{conf_file_name}" - ] = "true" - - def write(self, path: str) -> None: - with open(path, "w") as fd: - self._server_conf.write(fd) diff --git a/splunk_add_on_ucc_framework/templates/README/account_conf_spec.template b/splunk_add_on_ucc_framework/templates/README/account_conf_spec.template new file mode 100644 index 000000000..bdaa7adc4 --- /dev/null +++ b/splunk_add_on_ucc_framework/templates/README/account_conf_spec.template @@ -0,0 +1,4 @@ +{% for stanza in account_stanzas %} +[{{ stanza[0] }}] +{{ stanza[1] | sort() | join("\n") }} +{% endfor %} diff --git a/splunk_add_on_ucc_framework/commands/modular_alert_builder/arf_template/alert_actions.conf.spec.template b/splunk_add_on_ucc_framework/templates/README/alert_actions_conf_spec.template similarity index 89% rename from splunk_add_on_ucc_framework/commands/modular_alert_builder/arf_template/alert_actions.conf.spec.template rename to splunk_add_on_ucc_framework/templates/README/alert_actions_conf_spec.template index 280d9c390..e2d64d50e 100644 --- a/splunk_add_on_ucc_framework/commands/modular_alert_builder/arf_template/alert_actions.conf.spec.template +++ b/splunk_add_on_ucc_framework/templates/README/alert_actions_conf_spec.template @@ -3,5 +3,4 @@ {% for param in params %} {{ param }} {% endfor %} - -{% endfor %} \ No newline at end of file +{% endfor %} diff --git a/splunk_add_on_ucc_framework/templates/README/inputs_conf_spec.template b/splunk_add_on_ucc_framework/templates/README/inputs_conf_spec.template new file mode 100644 index 000000000..e6e09dc31 --- /dev/null +++ b/splunk_add_on_ucc_framework/templates/README/inputs_conf_spec.template @@ -0,0 +1,6 @@ +{% for stanza in input_stanzas %} +{{ "[" ~ stanza.keys() | join("") ~ "://]" }} + {% for property in stanza.values() -%} +{{ property | sort() | join("\n") }} + {% endfor %} +{% endfor %} diff --git a/splunk_add_on_ucc_framework/templates/README/settings_conf_spec.template b/splunk_add_on_ucc_framework/templates/README/settings_conf_spec.template new file mode 100644 index 000000000..c18a7b3c5 --- /dev/null +++ b/splunk_add_on_ucc_framework/templates/README/settings_conf_spec.template @@ -0,0 +1,4 @@ +{% for stanza in settings_stanzas %} +[{{ stanza[0] }}] +{{ stanza[1] | sort() | join("\n") }} +{% endfor %} diff --git a/splunk_add_on_ucc_framework/commands/modular_alert_builder/arf_template/alert_actions.conf.template b/splunk_add_on_ucc_framework/templates/conf_files/alert_actions_conf.template similarity index 61% rename from splunk_add_on_ucc_framework/commands/modular_alert_builder/arf_template/alert_actions.conf.template rename to splunk_add_on_ucc_framework/templates/conf_files/alert_actions_conf.template index 280d9c390..a9e35e2cf 100644 --- a/splunk_add_on_ucc_framework/commands/modular_alert_builder/arf_template/alert_actions.conf.template +++ b/splunk_add_on_ucc_framework/templates/conf_files/alert_actions_conf.template @@ -2,6 +2,8 @@ [{{ alert }}] {% for param in params %} {{ param }} +python.version: python3 +is_custom: 1 +payload_format: json {% endfor %} - -{% endfor %} \ No newline at end of file +{% endfor %} diff --git a/splunk_add_on_ucc_framework/templates/conf_files/app_conf.template b/splunk_add_on_ucc_framework/templates/conf_files/app_conf.template new file mode 100644 index 000000000..938e44d21 --- /dev/null +++ b/splunk_add_on_ucc_framework/templates/conf_files/app_conf.template @@ -0,0 +1,31 @@ +[launcher] +version = {{ addon_version }} +description = {{ description }} +author = {{ author }} + +[id] +version = {{ addon_version }} +name = {{ name }} + +[install] +build = {{ build }} +is_configured = false +state = enabled + +[package] +id = {{ id }} +check_for_updates = {{ check_for_updates }} + +[ui] +label = {{ label }} +{% if supported_themes %} +supported_themes = {{ supported_themes }} +{% endif %} +is_visible = {{ is_visible }} + +{% if custom_conf %} +[triggers] +{% for conf in custom_conf %} +reload.{{ conf }} = simple +{% endfor %} +{% endif %} diff --git a/splunk_add_on_ucc_framework/commands/modular_alert_builder/arf_template/eventtypes.conf.template b/splunk_add_on_ucc_framework/templates/conf_files/eventtypes_conf.template similarity index 66% rename from splunk_add_on_ucc_framework/commands/modular_alert_builder/arf_template/eventtypes.conf.template rename to splunk_add_on_ucc_framework/templates/conf_files/eventtypes_conf.template index a2466777d..d9b467dca 100644 --- a/splunk_add_on_ucc_framework/commands/modular_alert_builder/arf_template/eventtypes.conf.template +++ b/splunk_add_on_ucc_framework/templates/conf_files/eventtypes_conf.template @@ -1,5 +1,5 @@ {% for alert in mod_alerts %} - {% if alert.get("adaptive_response") and alert.adaptive_response.get("sourcetype") %} + {% if alert.get("adaptive_response", {}).get("sourcetype") %} [{{ alert.short_name }}_modaction_result] search = {{ 'sourcetype="' + alert.adaptive_response.sourcetype + '"' }} {% endif %} diff --git a/splunk_add_on_ucc_framework/templates/conf_files/inputs_conf.template b/splunk_add_on_ucc_framework/templates/conf_files/inputs_conf.template new file mode 100644 index 000000000..32b840bb9 --- /dev/null +++ b/splunk_add_on_ucc_framework/templates/conf_files/inputs_conf.template @@ -0,0 +1,4 @@ +{% for input_name in input_names %} +{{ "[" ~ input_name ~ "]"}} +python.version = python3 +{% endfor %} diff --git a/splunk_add_on_ucc_framework/templates/conf_files/restmap_conf.template b/splunk_add_on_ucc_framework/templates/conf_files/restmap_conf.template new file mode 100644 index 000000000..dd2e49f8b --- /dev/null +++ b/splunk_add_on_ucc_framework/templates/conf_files/restmap_conf.template @@ -0,0 +1,12 @@ +[admin:{{ namespace }}] +match = / +members = {{ endpoint_names }} + +{% for endpoint in endpoints %} +[admin_external:{{ endpoint.name }}] +handlertype = python +python.version = python3 +handlerfile = {{ endpoint.rh_name }}.py +handleractions = {{ endpoint.actions() | join(', ') }} +handlerpersistentmode = true +{% endfor %} diff --git a/splunk_add_on_ucc_framework/templates/conf_files/server_conf.template b/splunk_add_on_ucc_framework/templates/conf_files/server_conf.template new file mode 100644 index 000000000..b83497b73 --- /dev/null +++ b/splunk_add_on_ucc_framework/templates/conf_files/server_conf.template @@ -0,0 +1,4 @@ +[shclustering] +{% for conf in custom_conf %} +conf_replication_include.{{ conf }} = true +{% endfor %} diff --git a/splunk_add_on_ucc_framework/templates/conf_files/settings_conf.template b/splunk_add_on_ucc_framework/templates/conf_files/settings_conf.template new file mode 100644 index 000000000..4d6bb3c9f --- /dev/null +++ b/splunk_add_on_ucc_framework/templates/conf_files/settings_conf.template @@ -0,0 +1 @@ +{{ default_content }} diff --git a/splunk_add_on_ucc_framework/commands/modular_alert_builder/arf_template/tags.conf.template b/splunk_add_on_ucc_framework/templates/conf_files/tags_conf.template similarity index 100% rename from splunk_add_on_ucc_framework/commands/modular_alert_builder/arf_template/tags.conf.template rename to splunk_add_on_ucc_framework/templates/conf_files/tags_conf.template diff --git a/splunk_add_on_ucc_framework/templates/conf_files/web_conf.template b/splunk_add_on_ucc_framework/templates/conf_files/web_conf.template new file mode 100644 index 000000000..4cd6ceed4 --- /dev/null +++ b/splunk_add_on_ucc_framework/templates/conf_files/web_conf.template @@ -0,0 +1,9 @@ +{% for endpoint in endpoints %} +[expose:{{ endpoint.name }}] +pattern = {{ endpoint.name }} +methods = POST, GET + +[expose:{{ endpoint.name }}_specified] +pattern = {{ endpoint.name }}/* +methods = POST, GET, DELETE +{% endfor %} diff --git a/splunk_add_on_ucc_framework/commands/modular_alert_builder/arf_template/default_html_theme/textarea.html b/splunk_add_on_ucc_framework/templates/html_templates/textarea.html similarity index 100% rename from splunk_add_on_ucc_framework/commands/modular_alert_builder/arf_template/default_html_theme/textarea.html rename to splunk_add_on_ucc_framework/templates/html_templates/textarea.html diff --git a/splunk_add_on_ucc_framework/web_conf.py b/splunk_add_on_ucc_framework/web_conf.py deleted file mode 100644 index 095962c4f..000000000 --- a/splunk_add_on_ucc_framework/web_conf.py +++ /dev/null @@ -1,52 +0,0 @@ -# -# Copyright 2024 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 Sequence - -from splunk_add_on_ucc_framework.commands.rest_builder.endpoint.base import ( - RestEndpointBuilder, -) - - -class WebConf: - _template = """ -[expose:{name}] -pattern = {name} -methods = POST, GET -""" - - _specified_template = """ -[expose:{name}_specified] -pattern = {name}/* -methods = POST, GET, DELETE -""" - - @classmethod - def build(cls, endpoints: Sequence[RestEndpointBuilder]) -> str: - stanzas = [] - for endpoint in endpoints: - stanzas.append( - cls._template.format( - namespace=endpoint.namespace, - name=endpoint.name, - ) - ) - stanzas.append( - cls._specified_template.format( - namespace=endpoint.namespace, - name=endpoint.name, - ) - ) - return "".join(stanzas) diff --git a/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/README/splunk_ta_uccexample_account.conf.spec b/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/README/splunk_ta_uccexample_account.conf.spec index 44d8466d5..67a64483e 100644 --- a/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/README/splunk_ta_uccexample_account.conf.spec +++ b/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/README/splunk_ta_uccexample_account.conf.spec @@ -1,17 +1,17 @@ [] -custom_endpoint = -endpoint = +access_token = account_checkbox = -account_radio = account_multiple_select = -example_help_link = -username = -password = -token = +account_radio = +auth_type = client_id = client_secret = +custom_endpoint = +endpoint = +example_help_link = +instance_url = +password = redirect_url = -access_token = refresh_token = -instance_url = -auth_type = \ No newline at end of file +token = +username = \ No newline at end of file diff --git a/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/README/splunk_ta_uccexample_settings.conf.spec b/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/README/splunk_ta_uccexample_settings.conf.spec index 4328f2f12..a359f3958 100644 --- a/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/README/splunk_ta_uccexample_settings.conf.spec +++ b/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/README/splunk_ta_uccexample_settings.conf.spec @@ -1,20 +1,20 @@ [proxy] proxy_enabled = +proxy_password = +proxy_port = +proxy_rdns = proxy_type = proxy_url = -proxy_port = proxy_username = -proxy_password = -proxy_rdns = [logging] loglevel = [custom_abc] -testString = -testNumber = -testRegex = +testDate = testEmail = testIpv4 = -testDate = +testNumber = +testRegex = +testString = testUrl = \ No newline at end of file diff --git a/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/default/app.conf b/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/default/app.conf index e127851ca..ad55141fd 100644 --- a/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/default/app.conf +++ b/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/default/app.conf @@ -32,5 +32,4 @@ version = 1.0.0 [triggers] reload.splunk_ta_uccexample_account = simple reload.splunk_ta_uccexample_oauth = simple -reload.splunk_ta_uccexample_settings = simple - +reload.splunk_ta_uccexample_settings = simple \ No newline at end of file diff --git a/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/default/restmap.conf b/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/default/restmap.conf index c43d121fb..411bf07f8 100644 --- a/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/default/restmap.conf +++ b/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/default/restmap.conf @@ -1,4 +1,3 @@ - [admin:splunk_ta_uccexample] match = / members = splunk_ta_uccexample_account, splunk_ta_uccexample_oauth, splunk_ta_uccexample_settings @@ -22,4 +21,4 @@ handlertype = python python.version = python3 handlerfile = splunk_ta_uccexample_rh_settings.py handleractions = edit, list -handlerpersistentmode = true +handlerpersistentmode = true \ No newline at end of file diff --git a/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/default/server.conf b/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/default/server.conf index f89d933c0..5a7326859 100644 --- a/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/default/server.conf +++ b/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/default/server.conf @@ -1,4 +1,4 @@ [shclustering] conf_replication_include.splunk_ta_uccexample_settings = true conf_replication_include.splunk_ta_uccexample_account = true -conf_replication_include.splunk_ta_uccexample_oauth = true +conf_replication_include.splunk_ta_uccexample_oauth = true \ No newline at end of file diff --git a/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/default/web.conf b/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/default/web.conf index 9f58918dd..c1495c278 100644 --- a/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/default/web.conf +++ b/tests/testdata/expected_addons/expected_output_global_config_configuration/Splunk_TA_UCCExample/default/web.conf @@ -1,4 +1,3 @@ - [expose:splunk_ta_uccexample_account] pattern = splunk_ta_uccexample_account methods = POST, GET @@ -21,4 +20,4 @@ methods = POST, GET [expose:splunk_ta_uccexample_settings_specified] pattern = splunk_ta_uccexample_settings/* -methods = POST, GET, DELETE +methods = POST, GET, DELETE \ No newline at end of file diff --git a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/alert_actions.conf.spec b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/alert_actions.conf.spec index 12ca0db1e..46492994d 100644 --- a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/alert_actions.conf.spec +++ b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/alert_actions.conf.spec @@ -1,7 +1,8 @@ [test_alert] param._cam = Adaptive Response parameters. param.name = Name. It's a required parameter. It's default value is xyz. +param.description = Description. It's a required parameter. It's default value is some sample description. param.all_incidents = All Incidents. param.table_list = Table List. It's default value is problem. param.action = Action:. It's a required parameter. It's default value is update. -param.account = Select Account. It's a required parameter. +param.account = Select Account. It's a required parameter. \ No newline at end of file diff --git a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/inputs.conf.spec b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/inputs.conf.spec index eb0eade4d..d1d44e2c8 100644 --- a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/inputs.conf.spec +++ b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/inputs.conf.spec @@ -1,38 +1,38 @@ [example_input_one://] -input_one_checkbox = -input_one_radio = -singleSelectTest = -multipleSelectTest = -interval = -index = account = -object = -object_fields = -order_by = -use_existing_checkpoint = -start_date = -limit = -example_textarea_field = example_help_link = -hide_in_ui = -hard_disabled = +example_textarea_field = Help message +hard_disabled = +hide_in_ui = +index = Default: default +input_one_checkbox = This is an example checkbox for the input one entity Default: True +input_one_radio = This is an example radio button for the input one entity Default: yes +interval = Time interval of the data input, in seconds. +limit = The maximum number of results returned by the query. Default: 1000 +multipleSelectTest = Default: a|b +object = The name of the object to query for. +object_fields = Object fields from which to collect data. Delimit multiple fields using a comma. +order_by = The datetime field by which to query results in ascending order for indexing. Default: LastModifiedDate +singleSelectTest = +start_date = The datetime after which to query and index records, in this format: "YYYY-MM-DDThh:mm:ss.000z". Defaults to 90 days earlier from now. +use_existing_checkpoint = Data input already exists. Select `No` if you want to reset the data collection. Default: yes [example_input_two://] -interval = -index = account = -input_two_multiple_select = -input_two_checkbox = -input_two_radio = -use_existing_checkpoint = -start_date = -example_help_link = apis = -hide_in_ui = -hard_disabled = +example_help_link = +hard_disabled = +hide_in_ui = +index = Default: default +input_two_checkbox = This is an example checkbox for the input two entity +input_two_multiple_select = This is an example multipleSelect for input two entity +input_two_radio = This is an example radio button for the input two entity +interval = Time interval of the data input, in seconds. +start_date = The date and time, in "YYYY-MM-DDThh:mm:ss.000z" format, after which to query and index records. The default is 90 days before today. +use_existing_checkpoint = Data input already exists. Select `No` if you want to reset the data collection. Default: yes [example_input_three://] -interval = +interval = Time interval of the data input, in seconds. [example_input_four://] -interval = \ No newline at end of file +interval = Time interval of the data input, in seconds. diff --git a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/splunk_ta_uccexample_account.conf.spec b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/splunk_ta_uccexample_account.conf.spec index 06bdcd08f..f922e33d2 100644 --- a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/splunk_ta_uccexample_account.conf.spec +++ b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/splunk_ta_uccexample_account.conf.spec @@ -1,24 +1,24 @@ [] -custom_endpoint = -endpoint = -url = +access_token = account_checkbox = -account_radio = account_multiple_select = -example_help_link = -config1_help_link = -config2_help_link = -username = -password = -token = +account_radio = +auth_type = basic_oauth_text = client_id = client_secret = -redirect_url = -endpoint_token = +config1_help_link = +config2_help_link = +custom_endpoint = +endpoint = endpoint_authorize = +endpoint_token = +example_help_link = +instance_url = oauth_oauth_text = -access_token = +password = +redirect_url = refresh_token = -instance_url = -auth_type = \ No newline at end of file +token = +url = +username = \ No newline at end of file diff --git a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/splunk_ta_uccexample_settings.conf.spec b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/splunk_ta_uccexample_settings.conf.spec index 4328f2f12..a359f3958 100644 --- a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/splunk_ta_uccexample_settings.conf.spec +++ b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/README/splunk_ta_uccexample_settings.conf.spec @@ -1,20 +1,20 @@ [proxy] proxy_enabled = +proxy_password = +proxy_port = +proxy_rdns = proxy_type = proxy_url = -proxy_port = proxy_username = -proxy_password = -proxy_rdns = [logging] loglevel = [custom_abc] -testString = -testNumber = -testRegex = +testDate = testEmail = testIpv4 = -testDate = +testNumber = +testRegex = +testString = testUrl = \ No newline at end of file diff --git a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/bin/test_alert.py b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/bin/test_alert.py index c33336d17..a0db490ee 100644 --- a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/bin/test_alert.py +++ b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/bin/test_alert.py @@ -19,6 +19,10 @@ def validate_params(self): if not self.get_param("name"): self.log_error('name is a mandatory parameter, but its value is None.') return False + + if not self.get_param("description"): + self.log_error('description is a mandatory parameter, but its value is None.') + return False if not self.get_param("action"): self.log_error('action is a mandatory parameter, but its value is None.') diff --git a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/default/alert_actions.conf b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/default/alert_actions.conf index 77d525655..57834810c 100644 --- a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/default/alert_actions.conf +++ b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/default/alert_actions.conf @@ -1,13 +1,14 @@ [test_alert] -label = Test Alert -description = Description for test Alert Action -param._cam = {"task": ["Create", "Update"], "subject": ["endpoint"], "category": ["Information Conveyance", "Information Portrayal"], "technology": [{"version": ["1.0.0"], "product": "Test Incident Update", "vendor": "Splunk"}], "supports_adhoc": true, "supports_cloud": true, "drilldown_uri": "search?q=search%20index%3D\"_internal\"&earliest=0&latest="} +icon_path = test icon.png python.version = python3 is_custom = 1 payload_format = json -icon_path = test icon.png +label = Test Alert +description = Description for test Alert Action +param._cam = {"task": ["Create", "Update"], "subject": ["endpoint"], "category": ["Information Conveyance", "Information Portrayal"], "technology": [{"version": ["1.0.0"], "product": "Test Incident Update", "vendor": "Splunk"}], "supports_adhoc": true, "supports_cloud": true, "drilldown_uri": "search?q=search%20index%3D\"_internal\"&earliest=0&latest="} param.name = xyz +param.description = some sample description param.all_incidents = 0 param.table_list = problem param.action = update -param.account = +param.account = \ No newline at end of file diff --git a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/default/app.conf b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/default/app.conf index e541f2c34..51a81dada 100644 --- a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/default/app.conf +++ b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/default/app.conf @@ -24,5 +24,4 @@ version = 5.5.8R5fd76615 [triggers] reload.splunk_ta_uccexample_account = simple reload.splunk_ta_uccexample_oauth = simple -reload.splunk_ta_uccexample_settings = simple - +reload.splunk_ta_uccexample_settings = simple \ No newline at end of file diff --git a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/default/data/ui/alerts/test_alert.html b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/default/data/ui/alerts/test_alert.html index 5d91aae06..c655d084d 100644 --- a/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/default/data/ui/alerts/test_alert.html +++ b/tests/testdata/expected_addons/expected_output_global_config_everything/Splunk_TA_UCCExample/default/data/ui/alerts/test_alert.html @@ -9,6 +9,16 @@ +
+ +
+ + + Please enter the description for the alert + +
+