Skip to content

Commit

Permalink
refactor: introduce and reuse GlobalConfig class to manage globalConf…
Browse files Browse the repository at this point in the history
…ig file (#658)

* refactor: introduce GlobalConfig class

* refactor: introduce has_inputs method for GlobalConfig

* refactor: reuse GlobalConfig object in some smaller functions

* refactor: propagate global_config object to all other methods and classes

* refactor: get rid of _settings and _configs in GlobalConfig

* test: add simple unit test for GlobalConfig class
  • Loading branch information
artemrys authored Feb 20, 2023
1 parent c667430 commit c1c4e49
Show file tree
Hide file tree
Showing 11 changed files with 309 additions and 245 deletions.
2 changes: 1 addition & 1 deletion example/globalConfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@
"meta": {
"name": "Splunk_TA_dummy_data",
"restRoot": "Splunk_TA_dummy_data",
"version": "5.20.0Rc5bf18ae",
"version": "5.20.0R770b015b",
"displayName": "Splunk_TA_dummy_data",
"schemaVersion": "0.0.3"
}
Expand Down
155 changes: 52 additions & 103 deletions splunk_add_on_ucc_framework/commands/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,12 @@
# limitations under the License.
#
import configparser
import functools
import json
import logging
import os
import shutil
import sys
from typing import Optional

import yaml
from jinja2 import Environment, FileSystemLoader

from splunk_add_on_ucc_framework import (
Expand All @@ -35,6 +32,7 @@
meta_conf,
utils,
)
from splunk_add_on_ucc_framework import global_config as global_config_lib
from splunk_add_on_ucc_framework.commands.rest_builder import (
global_config_builder_schema,
global_config_post_processor,
Expand All @@ -54,28 +52,6 @@
loader=FileSystemLoader(os.path.join(internal_root_dir, "templates"))
)

Loader = getattr(yaml, "CSafeLoader", yaml.SafeLoader)
yaml_load = functools.partial(yaml.load, Loader=Loader)


def _update_ta_version(
config_path: str, addon_version: str, is_global_config_yaml: bool
):
"""
Update version of TA in globalConfig file.
"""

with open(config_path) as config_file:
if is_global_config_yaml:
schema_content = yaml_load(config_file)
else:
schema_content = json.load(config_file)
schema_content.setdefault("meta", {})["version"] = addon_version
if is_global_config_yaml:
utils.dump_yaml_config(schema_content, config_path)
else:
utils.dump_json_config(schema_content, config_path)


def _recursive_overwrite(src, dest, ignore_list=None):
"""
Expand Down Expand Up @@ -196,16 +172,15 @@ def _replace_oauth_html_template_token(ta_name, ta_version, outputdir):


def _modify_and_replace_token_for_oauth_templates(
ta_name, ta_tabs, ta_version, outputdir
ta_name: str, global_config: global_config_lib.GlobalConfig, outputdir: str
):
"""
Rename templates with respect to addon name if OAuth is configured.
Args:
ta_name (str): Name of TA.
ta_version (str): Version of TA.
ta_tabs (list): List of tabs mentioned in globalConfig file.
outputdir (str): output directory.
ta_name: Add-on name.
global_config: Object representing globalConfig.
outputdir: output directory.
"""
redirect_xml_src = os.path.join(
outputdir, ta_name, "default", "data", "ui", "views", "redirect.xml"
Expand All @@ -217,14 +192,14 @@ def _modify_and_replace_token_for_oauth_templates(
outputdir, ta_name, "appserver", "templates", "redirect.html"
)

if _is_oauth_configured(ta_tabs):
_replace_oauth_html_template_token(ta_name, ta_version, outputdir)
if _is_oauth_configured(global_config.tabs):
_replace_oauth_html_template_token(ta_name, global_config.version, outputdir)

redirect_js_dest = (
os.path.join(outputdir, ta_name, "appserver", "static", "js", "build", "")
+ ta_name.lower()
+ "_redirect_page."
+ ta_version
+ global_config.version
+ ".js"
)
redirect_html_dest = os.path.join(
Expand Down Expand Up @@ -254,18 +229,18 @@ def _modify_and_replace_token_for_oauth_templates(
os.remove(redirect_js_src)


def _add_modular_input(ta_name, schema_content, outputdir):
def _add_modular_input(
ta_name: str, global_config: global_config_lib.GlobalConfig, outputdir: str
):
"""
Generate Modular input for addon.
Args:
ta_name (str): Name of TA.
schema_content (dict): schema of globalConfig file.
outputdir (str): output directory.
ta_name: Add-on name.
global_config: Object representing globalConfig.
outputdir: output directory.
"""

services = schema_content.get("pages").get("inputs").get("services")
for service in services:
for service in global_config.inputs:
input_name = service.get("name")
class_name = input_name.upper()
description = service.get("title")
Expand Down Expand Up @@ -305,22 +280,23 @@ def _add_modular_input(ta_name, schema_content, outputdir):
config.write(configfile)


def _make_modular_alerts(ta_name, ta_namespace, schema_content, outputdir):
def _make_modular_alerts(
ta_name: str, global_config: global_config_lib.GlobalConfig, outputdir: str
):
"""
Generate the alert schema with required structure.
Args:
ta_name (str): Name of TA.
ta_namespace (str): restRoot of TA.
schema_content (dict): schema of globalConfig file.
outputdir (str): output directory.
ta_name: Add-on name.
global_config: Object representing globalConfig.
outputdir: Output directory.
"""

if schema_content.get("alerts"):
if global_config.content.get("alerts"):
alert_build(
{"alerts": schema_content["alerts"]},
{"alerts": global_config.content["alerts"]},
ta_name,
ta_namespace,
global_config.namespace,
outputdir,
internal_root_dir,
)
Expand Down Expand Up @@ -447,29 +423,12 @@ def generate(
if outputdir is None:
outputdir = os.path.join(os.getcwd(), "output")
addon_version = _get_addon_version(addon_version)

if not os.path.exists(source):
raise NotADirectoryError(f"{os.path.abspath(source)} not found.")

# Setting default value to Config argument
if not config_path:
is_global_config_yaml = False
config_path = os.path.abspath(
os.path.join(source, PARENT_DIR, "globalConfig.json")
)
if not os.path.isfile(config_path):
config_path = os.path.abspath(
os.path.join(source, PARENT_DIR, "globalConfig.yaml")
)
is_global_config_yaml = True
else:
is_global_config_yaml = True if config_path.endswith(".yaml") else False

logger.info(f"Cleaning out directory {outputdir}")
shutil.rmtree(os.path.join(outputdir), ignore_errors=True)
os.makedirs(os.path.join(outputdir))
logger.info(f"Cleaned out directory {outputdir}")

app_manifest_path = os.path.abspath(
os.path.join(source, app_manifest.APP_MANIFEST_FILE_NAME),
)
Expand All @@ -486,48 +445,41 @@ def generate(
)
sys.exit(1)
ta_name = manifest.get_addon_name()
if not config_path:
is_global_config_yaml = False
config_path = os.path.abspath(os.path.join(source, "..", "globalConfig.json"))
if not os.path.isfile(config_path):
config_path = os.path.abspath(
os.path.join(source, "..", "globalConfig.yaml")
)
is_global_config_yaml = True
else:
is_global_config_yaml = True if config_path.endswith(".yaml") else False

if os.path.isfile(config_path):
global_config = global_config_lib.GlobalConfig()
global_config.parse(config_path, is_global_config_yaml)
try:
with open(config_path) as f_config:
config_raw = f_config.read()
config_content = (
yaml_load(config_raw)
if is_global_config_yaml
else json.loads(config_raw)
)
validator = global_config_validator.GlobalConfigValidator(
internal_root_dir, config_content
internal_root_dir, global_config
)
validator.validate()
logger.info("Config is valid")
logger.info("globalConfig file is valid")
except global_config_validator.GlobalConfigValidatorException as e:
logger.error(f"Config is not valid. Error: {e}")
logger.error(f"globalConfig file is not valid. Error: {e}")
sys.exit(1)

_update_ta_version(config_path, addon_version, is_global_config_yaml)

schema_content = global_config_update.handle_global_config_update(
config_path, is_global_config_yaml
)

global_config.update_addon_version(addon_version)
global_config.dump(global_config.original_path)
global_config_update.handle_global_config_update(global_config)
scheme = global_config_builder_schema.GlobalConfigBuilderSchema(
schema_content, j2_env
global_config, j2_env
)

addon_version = schema_content.get("meta").get("version")
logger.info("Addon Version : " + addon_version)
ta_tabs = schema_content.get("pages").get("configuration").get("tabs")
ta_namespace = schema_content.get("meta").get("restRoot")
is_inputs = "inputs" in schema_content.get("pages")

logger.info("Package ID is " + ta_name)

logger.info(f"Building add-on with version {addon_version}")
logger.info(f"Package ID is {ta_name}")
logger.info("Copy UCC template directory")
_recursive_overwrite(
os.path.join(internal_root_dir, "package"), os.path.join(outputdir, ta_name)
)

logger.info("Copy globalConfig to output")
global_config_file = (
"globalConfig.yaml" if is_global_config_yaml else "globalConfig.json"
Expand All @@ -553,15 +505,14 @@ def generate(
except SplunktaucclibNotFound as e:
logger.error(str(e))
sys.exit(1)

_replace_token(ta_name, outputdir)

_generate_rest(ta_name, scheme, outputdir)

_modify_and_replace_token_for_oauth_templates(
ta_name, ta_tabs, schema_content.get("meta").get("version"), outputdir
ta_name,
global_config,
outputdir,
)
if is_inputs:
if global_config.has_inputs():
default_no_input_xml_file = os.path.join(
outputdir,
ta_name,
Expand All @@ -572,14 +523,12 @@ def generate(
"default_no_input.xml",
)
os.remove(default_no_input_xml_file)
_add_modular_input(ta_name, schema_content, outputdir)
_add_modular_input(ta_name, global_config, outputdir)
else:
_handle_no_inputs(ta_name, outputdir)

_make_modular_alerts(ta_name, ta_namespace, schema_content, outputdir)

_make_modular_alerts(ta_name, global_config, outputdir)
else:
logger.info("Addon Version : " + addon_version)
logger.info(f"Building add-on with version {addon_version}")
logger.warning(
"Skipped generating UI components as globalConfig file does not exist."
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import json
from typing import Any, Dict, List, Type

from splunk_add_on_ucc_framework import global_config as global_config_lib

from splunk_add_on_ucc_framework.commands.rest_builder.endpoint.base import (
RestEndpointBuilder,
)
Expand Down Expand Up @@ -56,27 +58,26 @@ def _is_true(val):


class GlobalConfigBuilderSchema:
def __init__(self, content, j2_env):
self._content = content
self._inputs = []
def __init__(self, global_config: global_config_lib.GlobalConfig, j2_env):
self.global_config = global_config
self._configs = []
self._settings = []
for configuration in self.global_config.tabs:
if "table" in configuration:
self._configs.append(configuration)
else:
self._settings.append(configuration)
self.j2_env = j2_env
self._endpoints: Dict[str, RestEndpointBuilder] = {}
self._parse()
self._parse_builder_schema()

@property
def product(self) -> str:
return self._meta["name"]
return self.global_config.product

@property
def namespace(self) -> str:
return self._meta["restRoot"]

@property
def inputs(self):
return self._inputs
return self.global_config.namespace

@property
def configs(self):
Expand All @@ -90,26 +91,6 @@ def settings(self):
def endpoints(self) -> List[RestEndpointBuilder]:
return list(self._endpoints.values())

def _parse(self):
self._meta = self._content["meta"]
pages = self._content["pages"]
self._parse_configuration(pages.get("configuration"))
self._parse_inputs(pages.get("inputs"))

def _parse_configuration(self, configurations):
if not configurations or "tabs" not in configurations:
return
for configuration in configurations["tabs"]:
if "table" in configuration:
self._configs.append(configuration)
else:
self._settings.append(configuration)

def _parse_inputs(self, inputs):
if not inputs or "services" not in inputs:
return
self._inputs = inputs["services"]

def _parse_builder_schema(self):
self._builder_configs()
self._builder_settings()
Expand All @@ -136,7 +117,9 @@ def _builder_configs(self):
for entity_element in config["entity"]:
if entity_element["type"] == "oauth":
self._get_endpoint(
"oauth", OAuthModelEndpointBuilder, app_name=self._meta["name"]
"oauth",
OAuthModelEndpointBuilder,
app_name=self.global_config.product,
)

def _builder_settings(self):
Expand All @@ -156,7 +139,7 @@ def _builder_settings(self):
endpoint_obj.add_entity(entity)

def _builder_inputs(self):
for input_item in self._inputs:
for input_item in self.global_config.inputs:
rest_handler_name = input_item.get("restHandlerName")
rest_handler_module = input_item.get(
"restHandlerModule",
Expand Down Expand Up @@ -213,7 +196,7 @@ def _get_endpoint(
if name not in self._endpoints:
endpoint = endpoint_builder(
name=name,
namespace=self._meta["restRoot"],
namespace=self.global_config.namespace,
j2_env=self.j2_env,
**kwargs,
)
Expand Down
Loading

0 comments on commit c1c4e49

Please sign in to comment.