Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
61f3a20
feat: generate minimal globalConfig
hetangmodi-crest Mar 7, 2025
db9ddbb
tests: add test cases
hetangmodi-crest Mar 7, 2025
b3f9548
docs: updated docs regarding generated conf, xml and html files
srv-rr-github-token Mar 7, 2025
c1225ce
chore: updated class names
hetangmodi-crest Mar 10, 2025
9ce8468
docs: update doc_generator logic
hetangmodi-crest Mar 10, 2025
b0020db
chore: updated encoding logic
hetangmodi-crest Mar 10, 2025
fcb7825
tests: add unit test cases and updated coverage percentage
hetangmodi-crest Mar 13, 2025
70a0c32
chore: updating coverage for Python3.10
hetangmodi-crest Mar 13, 2025
46ea73c
Merge branch 'develop' into feat/create-minimal-globalConfig
vtsvetkov-splunk Mar 14, 2025
0f4975e
Merge branch 'develop' into feat/create-minimal-globalConfig
hetangmodi-crest Mar 17, 2025
73c687b
refactor: shifted implementation from FileGenerator to GlobalConfig c…
hetangmodi-crest Mar 19, 2025
e959ada
docs: updated docs regarding generated conf, xml and html files
srv-rr-github-token Mar 19, 2025
8ac87e8
test(unit): add test to increase coverage
hetangmodi-crest Mar 21, 2025
c7d552a
chore: merge branch 'develop' into feat/create-minimal-globalConfig
hetangmodi-crest Mar 21, 2025
9506c10
chore: remove unwanted code
hetangmodi-crest Mar 21, 2025
eabca2b
chore: update min coverage
hetangmodi-crest Mar 21, 2025
a0485fd
refactor: updated the generation of minimal globalConfig
hetangmodi-crest Mar 24, 2025
57d7a85
chore: leverage dump method of GlobalConfig
hetangmodi-crest Mar 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ omit =
splunk_add_on_ucc_framework/templates/input.module-template

[report]
fail_under = 81.5
fail_under = 84.60
1 change: 1 addition & 0 deletions docs/generated_files.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ The following table describes the files generated by UCC framework.
| inputs.xml | output/<YOUR_ADDON_NAME>/default/data/ui/views | Generates inputs.xml based on inputs configuration present in globalConfig, in `default/data/ui/views/inputs.xml` folder |
| _redirect.xml | output/<YOUR_ADDON_NAME>/default/data/ui/views | Generates ta_name_redirect.xml file, if oauth is mentioned in globalConfig, in `default/data/ui/views/` folder. |
| _.html | output/<YOUR_ADDON_NAME>/default/data/ui/alerts | Generates `alert_name.html` file based on alerts configuration present in globalConfig, in `default/data/ui/alerts` folder. |
| globalConfig.json | <source_dir> | Generates globalConfig.json file in the source code if globalConfig is not present in source directory at build time. |

244 changes: 121 additions & 123 deletions splunk_add_on_ucc_framework/commands/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
)
from splunk_add_on_ucc_framework.generators.file_generator import begin
from splunk_add_on_ucc_framework.generators.conf_files.create_app_conf import AppConf
import addonfactory_splunk_conf_parser_lib as conf_parser


logger = logging.getLogger("ucc_gen")
Expand Down Expand Up @@ -442,137 +443,138 @@ def generate(
generated_files = []

gc_path = _get_and_check_global_config_path(source, config_path)
if gc_path:
logger.info(f"Using globalConfig file located @ {gc_path}")
global_config = global_config_lib.GlobalConfig(gc_path)
global_config.cleanup_unwanted_params()
# handle the update of globalConfig before validating
global_config_update.handle_global_config_update(global_config)
try:
validator = global_config_validator.GlobalConfigValidator(
internal_root_dir, global_config
)
validator.validate()
logger.info("globalConfig file is valid")
except exceptions.GlobalConfigValidatorException as e:
logger.error(f"globalConfig file is not valid. Error: {e}")
sys.exit(1)
global_config.update_addon_version(addon_version)
global_config.dump(global_config.original_path)
logger.info(
f"Updated and saved add-on version in the globalConfig file to {addon_version}"
)
global_config.add_ucc_version(__version__)
global_config.expand()
if ta_name != global_config.product:
logger.error(
"Add-on name mentioned in globalConfig meta tag and that app.manifest are not same,"
"please unify them to build the add-on."
)
sys.exit(1)
global_config.parse_user_defined_handlers()
scheme = global_config_builder_schema.GlobalConfigBuilderSchema(global_config)
if global_config.has_pages():
utils.recursive_overwrite(
os.path.join(internal_root_dir, "package"),
os.path.join(output_directory, ta_name),
ui_source_map,
)
global_config_file = (
"globalConfig.yaml" if gc_path.endswith(".yaml") else "globalConfig.json"
)
output_build_path = os.path.join(
output_directory, ta_name, "appserver", "static", "js", "build"

if not gc_path:
app_conf_path = os.path.join(source, "default", "app.conf")
app_conf_content = {}
if os.path.isfile(app_conf_path):
# read only if app.conf exists in source code
app_conf = conf_parser.TABConfigParser()
app_conf.read(app_conf_path)
app_conf_content = app_conf.item_dict()

global_config = global_config_lib.GlobalConfig(
gc_path,
source=source,
app_manifest=app_manifest,
app_conf_content=app_conf_content,
)
if not os.path.isdir(output_build_path):
# this path may not exist for the .conf-only add-ons
os.makedirs(output_build_path)
global_config.dump(os.path.join(output_build_path, global_config_file))
logger.info("Copied globalConfig to output")
ucc_lib_target = os.path.join(output_directory, ta_name, "lib")
try:
install_python_libraries(
source,
ucc_lib_target,
python_binary_name,
includes_ui=True,
os_libraries=global_config.os_libraries,
pip_version=pip_version,
pip_legacy_resolver=pip_legacy_resolver,
pip_custom_flag=pip_custom_flag,
includes_oauth=global_config.has_oauth(),
)
except SplunktaucclibNotFound as e:
logger.error(str(e))
sys.exit(1)
logger.info(
f"Installed add-on requirements into {ucc_lib_target} from {source}"
gc_path = os.path.join(source, os.pardir, "globalConfig.json")
global_config.dump(gc_path)
logger.info(f"Created minimal globalConfig file located @ {gc_path}")

logger.info(f"Using globalConfig file located @ {gc_path}")
global_config = global_config_lib.GlobalConfig(gc_path)
global_config.cleanup_unwanted_params()
# handle the update of globalConfig before validating
global_config_update.handle_global_config_update(global_config)
try:
validator = global_config_validator.GlobalConfigValidator(
internal_root_dir, global_config
)
generated_files.extend(
begin(
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=global_config.meta.get("isVisible", True),
)
validator.validate()
logger.info("globalConfig file is valid")
except exceptions.GlobalConfigValidatorException as e:
logger.error(f"globalConfig file is not valid. Error: {e}")
sys.exit(1)
global_config.update_addon_version(addon_version)
global_config.dump(global_config.original_path)
logger.info(
f"Updated and saved add-on version in the globalConfig file to {addon_version}"
)
global_config.add_ucc_version(__version__)
global_config.expand()
if ta_name != global_config.product:
logger.error(
"Add-on name mentioned in globalConfig meta tag and that app.manifest are not same,"
"please unify them to build the add-on."
)
# TODO: all FILES GENERATED object: generated_files, use it for comparison
if global_config.has_pages():
builder_obj = RestBuilder(scheme, os.path.join(output_directory, ta_name))
builder_obj.build()
_modify_and_replace_token_for_oauth_templates(
ta_name,
global_config,
output_directory,
)
if global_config.has_inputs():
logger.info("Generating inputs code")
_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, 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))

if global_config.has_dashboard():
logger.info("Including dashboard")
dashboard_definition_json_path = os.path.join(
output_directory,
ta_name,
"appserver",
"static",
"js",
"build",
"custom",
)
dashboard.generate_dashboard(
global_config, ta_name, dashboard_definition_json_path
)

else:
global_config = None
conf_file_names = []
logger.warning(
"Skipped generating UI components as globalConfig file does not exist"
sys.exit(1)
global_config.parse_user_defined_handlers()
scheme = global_config_builder_schema.GlobalConfigBuilderSchema(global_config)
if global_config.has_pages():
utils.recursive_overwrite(
os.path.join(internal_root_dir, "package"),
os.path.join(output_directory, ta_name),
ui_source_map,
)
ucc_lib_target = os.path.join(output_directory, ta_name, "lib")
global_config_file = (
"globalConfig.yaml" if gc_path.endswith(".yaml") else "globalConfig.json"
)
output_build_path = os.path.join(
output_directory, ta_name, "appserver", "static", "js", "build"
)
if not os.path.isdir(output_build_path):
# this path may not exist for the .conf-only add-ons
os.makedirs(output_build_path)
global_config.dump(os.path.join(output_build_path, global_config_file))
logger.info("Copied globalConfig to output")
ucc_lib_target = os.path.join(output_directory, ta_name, "lib")
ui_available = False
if global_config and global_config.has_pages():
ui_available = global_config.meta.get("isVisible", True)
try:
install_python_libraries(
source,
ucc_lib_target,
python_binary_name,
includes_ui=ui_available,
os_libraries=global_config.os_libraries,
pip_version=pip_version,
pip_legacy_resolver=pip_legacy_resolver,
pip_custom_flag=pip_custom_flag,
includes_oauth=global_config.has_oauth(),
)
logger.info(
f"Installed add-on requirements into {ucc_lib_target} from {source}"
except SplunktaucclibNotFound as e:
logger.error(str(e))
sys.exit(1)
logger.info(f"Installed add-on requirements into {ucc_lib_target} from {source}")
generated_files.extend(
begin(
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=global_config.meta.get("isVisible", True),
)
)
# TODO: all FILES GENERATED object: generated_files, use it for comparison
if global_config.has_pages():
builder_obj = RestBuilder(scheme, os.path.join(output_directory, ta_name))
builder_obj.build()
_modify_and_replace_token_for_oauth_templates(
ta_name,
global_config,
output_directory,
)
if global_config.has_inputs():
logger.info("Generating inputs code")
_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, 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))

if global_config.has_dashboard():
logger.info("Including dashboard")
dashboard_definition_json_path = os.path.join(
output_directory,
ta_name,
"appserver",
"static",
"js",
"build",
"custom",
)
dashboard.generate_dashboard(
global_config, ta_name, dashboard_definition_json_path
)

ignore_list = _get_ignore_list(
Expand Down Expand Up @@ -613,10 +615,6 @@ def generate(
logger.info(
f"Updated {app_manifest_lib.APP_MANIFEST_FILE_NAME} file in the output folder"
)

ui_available = False
if global_config and global_config.has_pages():
ui_available = global_config.meta.get("isVisible", True)
# NOTE: merging source and generated 'app.conf' as per previous design
AppConf(
global_config=global_config,
Expand Down
6 changes: 6 additions & 0 deletions splunk_add_on_ucc_framework/generators/doc_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ def generate_docs() -> None:
f"| {f_tup.file_name} | output/<YOUR_ADDON_NAME>/{'/'.join(f_tup.file_path)} "
f"| {f_tup.file_description} |"
)
# statically appending this line as we are generating it now
doc_content.append(
"| globalConfig.json | <source_dir> | "
"Generates globalConfig.json file in the source code if globalConfig "
"is not present in source directory at build time. |"
)
doc_content.append("\n")
with open(
join(
Expand Down
69 changes: 58 additions & 11 deletions splunk_add_on_ucc_framework/global_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
from dataclasses import dataclass, field, fields

import yaml
import os
from splunk_add_on_ucc_framework import app_manifest as app_manifest_lib

from splunk_add_on_ucc_framework import utils
from splunk_add_on_ucc_framework.commands.rest_builder.user_defined_rest_handlers import (
Expand Down Expand Up @@ -60,20 +62,65 @@ def from_dict(cls, **kwargs: Any) -> "OSDependentLibraryConfig":


class GlobalConfig:
def __init__(self, global_config_path: str) -> None:
with open(global_config_path) as f_config:
config_raw = f_config.read()
self._is_global_config_yaml = (
True if global_config_path.endswith(".yaml") else False
)
self._content = (
yaml_load(config_raw)
if self._is_global_config_yaml
else json.loads(config_raw)
)
def __init__(self, global_config_path: str, **kwargs: Any) -> None:
Copy link
Member

Choose a reason for hiding this comment

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

@hetangmodi-crest this conflicts with https://github.com/splunk/addonfactory-ucc-generator/pull/1655/files, please review that PR first and let's merge it.

I'd like to keep the __init__ method how it is in that PR.

if global_config_path == "":
global_config_path = self.from_app_conf_and_app_manifest(
kwargs["source"], kwargs["app_manifest"], kwargs["app_conf_content"]
)
else:
with open(global_config_path) as f_config:
config_raw = f_config.read()
self._is_global_config_yaml = (
True if global_config_path.endswith(".yaml") else False
)
self._content = (
yaml_load(config_raw)
if self._is_global_config_yaml
else json.loads(config_raw)
)
self._original_path = global_config_path
self.user_defined_handlers = UserDefinedRestHandlers()

def from_app_conf_and_app_manifest(
self,
source_dir: str,
app_manifest: app_manifest_lib.AppManifest,
app_conf_content: Dict[str, Any],
) -> str:
Copy link
Member

Choose a reason for hiding this comment

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

It should be a classmethod and it should return "GlobalConfig".

Copy link
Member

Choose a reason for hiding this comment

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

And please make it a separate PR after #1655 is merged.

check_for_update = app_conf_content.get("package", {}).get(
"check_for_updates", "true"
)
# checkForUpdates is by default set to 'true'
check_for_update = check_for_update.lower() in ("true", "1", "t", "y", "yes")
supported_themes = app_conf_content.get("ui", {}).get("supported_themes", "")
if supported_themes:
supported_themes = [item.strip() for item in supported_themes.split(",")]
minimal_gc_path = os.path.join(source_dir, os.pardir, "globalConfig.json")
self._is_global_config_yaml = False

def create_globalConfig(
app_manifest: app_manifest_lib.AppManifest,
check_for_update: bool,
supported_themes: List[str],
) -> Dict[str, Any]:
minimal_gc = {
"meta": {
"name": f"{app_manifest.get_addon_name()}",
"restRoot": f"{app_manifest.get_addon_name()}",
"displayName": f"{app_manifest.get_title()}",
"version": f"{app_manifest.get_addon_version()}",
"checkForUpdates": check_for_update,
}
}
if supported_themes:
minimal_gc["meta"]["supportedThemes"] = supported_themes
return minimal_gc

self._content = create_globalConfig(
app_manifest, check_for_update, supported_themes
)
return minimal_gc_path

def parse_user_defined_handlers(self) -> None:
"""Parse user-defined REST handlers from globalConfig["options"]["restHandlers"]"""
rest_handlers = self._content.get("options", {}).get("restHandlers", [])
Expand Down
Loading
Loading