Skip to content
42 changes: 35 additions & 7 deletions splunk_add_on_ucc_framework/commands/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,27 @@
internal_root_dir = os.path.dirname(os.path.dirname(__file__))


def _inject_app_name_in_base_html(ta_name: str, outputdir: str) -> None:
"""
Replace __APP_NAME__ placeholder in base.html with the actual add-on name.

Args:
ta_name: Add-on name.
outputdir: output directory.
"""
base_html_path = os.path.join(
outputdir, ta_name, "appserver", "templates", "base.html"
)
if not os.path.isfile(base_html_path):
return
with open(base_html_path) as f:
content = f.read()
with open(base_html_path, "w") as f:
# Splunk static app URLs are keyed by the real app name from the package.
# Lowercasing here breaks asset resolution for mixed-case app names.
f.write(content.replace("__APP_NAME__", ta_name))


def _modify_and_replace_token_for_oauth_templates(
ta_name: str, global_config: global_config_lib.GlobalConfig, outputdir: str
) -> None:
Expand All @@ -92,8 +113,9 @@ def _modify_and_replace_token_for_oauth_templates(
s = f.read()

with open(os.path.join(html_template_path, "redirect.html"), "w") as f:
s = s.replace("${ta.name}", ta_name.lower())
s = s.replace("${ta.version}", global_config.version)
s = s.replace("__APP_NAME__", ta_name)
s = s.replace("__TA_NAME__", ta_name.lower())
s = s.replace("__TA_VERSION__", global_config.version)
f.write(s)

redirect_js_dest = (
Expand Down Expand Up @@ -608,11 +630,6 @@ def generate(
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, gc_path)
Expand Down Expand Up @@ -646,6 +663,17 @@ def generate(
utils.recursive_overwrite(source, os.path.join(output_directory, ta_name))
logger.info("Copied package directory")

# Apply template placeholder replacement on the final copied output.
# Running this before recursive_overwrite causes package templates to
# overwrite the transformed files and leaves placeholders behind.
if global_config.has_pages():
_modify_and_replace_token_for_oauth_templates(
ta_name,
global_config,
output_directory,
)
_inject_app_name_in_base_html(ta_name, output_directory)

default_meta_conf_path = os.path.join(
output_directory, ta_name, "metadata", meta_conf_lib.DEFAULT_META_FILE_NAME
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,20 @@
~ limitations under the License.
~
-->
<%!
from splunk.appserver.mrsparkle.lib import util

app_name = cherrypy.request.path_info.split('/')[3]
%>\
<!DOCTYPE html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>${_('Loading')}</title>
<title>Loading</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="shortcut icon" href="${util.getFaviconURL()}" />
<link rel="apple-touch-icon" href="apple-touch-icon.png" />
<base >
<base href="../../" />
<script src="config?autoload=1" crossorigin="use-credentials"></script>
<script src="static/js/i18n.js"></script>
<script src="i18ncatalog?autoload=1"></script>
</head>

<body>
<script src="${make_url('/config?autoload=1')}" crossorigin="use-credentials"></script>
<script src="${make_url('/static/js/i18n.js')}"></script>
<script src="${make_url('/i18ncatalog?autoload=1')}"></script>
<script>
__splunkd_partials__ = ${json_decode(splunkd)};
</script>

<% page_path = "/static/app/" + app_name + "/js/build/entry_page.js" %>

<script type="module" src="${make_url(page_path)}"></script>
<script type="module" src="static/app/__APP_NAME__/js/build/entry_page.js"></script>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,23 @@
~ limitations under the License.
~
-->
<%! app_name = cherrypy.request.path_info.split('/')[3] %>\
<!DOCTYPE html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>${_('Loading')}</title>
<title>Loading</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="apple-touch-icon" href="apple-touch-icon.png" />
<base href="../../" />
<script src="config?autoload=1" crossorigin="use-credentials"></script>
<script src="static/js/i18n.js"></script>
<script src="i18ncatalog?autoload=1"></script>
</head>

<body>
<script src="${make_url('/config?autoload=1')}" crossorigin="use-credentials"></script>
<script src="${make_url('/static/js/i18n.js')}"></script>
<script src="${make_url('/i18ncatalog?autoload=1')}"></script>
<script>
__splunkd_partials__ = ${json_decode(splunkd)};
</script>

<% page_path = "/static/app/" + app_name + "/js/build/${ta.name}_redirect_page.${ta.version}.js" %>

<script type="module" src="${make_url(page_path)}"></script>
<script
type="module"
src="static/app/__APP_NAME__/js/build/__TA_NAME___redirect_page.__TA_VERSION__.js"
></script>
</body>
</html>
100 changes: 65 additions & 35 deletions splunk_add_on_ucc_framework/package_files_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,55 +14,85 @@
# limitations under the License.
#
import logging
import os
import re
from collections import namedtuple
from pathlib import Path
from typing import Optional
from typing import Callable

logger = logging.getLogger("ucc_gen")
FileUpdater = namedtuple("FileUpdater", ["path_segments", "function"])

internal_root_dir = Path(__file__).resolve().parent

_base_html_pattern = re.compile(
r"<script\s+src=\"\${make_url\(page_path\)}\"\s*>\s*</script>"
)

def _load_canonical_template(template_name: str) -> str:
return (
internal_root_dir / "package" / "appserver" / "templates" / template_name
).read_text()

def _handle_base_html_update(content: str) -> Optional[str]:
matches = _base_html_pattern.findall(content)

if not matches:
return None
def _is_legacy_base_html(content: str) -> bool:
legacy_markers = (
"cherrypy.request.path_info",
"${make_url('/config?autoload=1')}",
"__splunkd_partials__ = ${json_decode(splunkd)};",
'<script type="module" src="${make_url(page_path)}"></script>',
'<script src="${make_url(page_path)}"></script>',
'page_path = "/static/app/" + app_name + "/js/build/entry_page.js"',
)
return any(marker in content for marker in legacy_markers)

for result in matches:
content = content.replace(
result, '<script type="module" src="${make_url(page_path)}"></script>'
)

return content
def _is_legacy_redirect_html(content: str) -> bool:
legacy_markers = (
"cherrypy.request.path_info",
"${make_url('/config?autoload=1')}",
"__splunkd_partials__ = ${json_decode(splunkd)};",
"${ta.name}",
"${ta.version}",
'page_path = "/static/app/" + app_name + "/js/build/${ta.name}_redirect_page.${ta.version}.js"',
)
return any(marker in content for marker in legacy_markers)


def handle_package_files_update(path: str) -> None:
files_to_update = [
FileUpdater(("appserver", "templates", "base.html"), _handle_base_html_update)
]
def _migrate_template_if_needed(
package_dir: str,
relative_path: str,
template_name: str,
detector: Callable[[str], bool],
) -> None:
file_path = Path(package_dir) / relative_path
if not file_path.is_file():
return

for file_updater in files_to_update:
relative_path = os.path.join(*file_updater.path_segments)
file_path = os.path.join(path, *file_updater.path_segments)
current_content = file_path.read_text()
if current_content == _load_canonical_template(template_name):
return

if not os.path.isfile(file_path):
continue
if detector(current_content):
file_path.write_text(_load_canonical_template(template_name))
logger.info(
"File '%s' exists in the package directory and was updated to the "
"current static UCC template to remove legacy Mako/CherryPy usage.",
relative_path,
)
return

path_obj = Path(file_path)
original_content = path_obj.read_text()
output_content = file_updater.function(original_content)
logger.warning(
"File '%s' exists in the package directory and uses a custom template. "
"UCC left it unchanged because it could not be safely migrated "
"automatically.",
relative_path,
)

if output_content is None or original_content == output_content:
continue

path_obj.write_text(output_content)
logger.info(
f"File '{relative_path}' exists in the package directory and its content needed to be updated by UCC."
)
def handle_package_files_update(path: str) -> None:
_migrate_template_if_needed(
path,
"appserver/templates/base.html",
"base.html",
_is_legacy_base_html,
)
_migrate_template_if_needed(
path,
"appserver/templates/redirect.html",
"redirect.html",
_is_legacy_redirect_html,
)
Loading
Loading