Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/core_lib_handler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# core_lib_handler.py

::: solnlib.core_lib_handler
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ nav:
- "acl.py": acl.md
- "credentials.py": credentials.md
- "conf_manager.py": conf_manager.md
- "core_lib_handler.py": core_lib_handler.md
- "file_monitor.py": file_monitor.md
- "hec_config.py": hec_config.md
- "log.py": log.md
Expand Down
2 changes: 2 additions & 0 deletions solnlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
acl,
bulletin_rest_client,
conf_manager,
core_lib_handler,
credentials,
file_monitor,
hec_config,
Expand All @@ -40,6 +41,7 @@
"acl",
"bulletin_rest_client",
"conf_manager",
"core_lib_handler",
"credentials",
"file_monitor",
"hec_config",
Expand Down
129 changes: 129 additions & 0 deletions solnlib/core_lib_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
#
# Copyright 2025 Splunk Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import os
import sys
import re
from types import ModuleType
import shutil
import importlib

def _is_module_from_splunk_core(lib_module: ModuleType) -> bool:
"""
Check if the imported module is from the Splunk-provided libraries.
"""
core_site_packages_regex = _get_core_site_packages_regex()

splunk_site_packages_paths = [
path for path in sys.path if core_site_packages_regex.search(path)
]

return any(
_is_core_site_package_path(
splunk_site_packages_path, lib_module.__name__, lib_module.__file__
)
for splunk_site_packages_path in splunk_site_packages_paths
)


def _is_core_site_package_path(
core_site_packages_directory: str, module_name: str, module_path: str
) -> bool:
"""
Check if the module path originates from a core site-packages directory.
"""
return os.path.join(core_site_packages_directory, module_name) in module_path


def _get_core_site_packages_regex() -> re.Pattern:
"""
Get the regex pattern for matching site-packages directories.
"""
sep = os.path.sep
sep_escaped = re.escape(sep)

return (
re.compile(
r"Python(?:-\d+(?:\.\d+)?)?"
+ sep_escaped
+ r"lib"
+ sep_escaped
+ r"site-packages$",
re.IGNORECASE,
)
if sys.platform.startswith("win32")
else re.compile(
r"lib"
+ r"("
+ sep_escaped
+ r"python\d+(\.\d+)?"
+ r")?"
+ sep_escaped
+ r"site-packages$"
)
)


def _cache_lib(lib_name: str):
"""
Import the Splunk-shipped library first, before adding TA paths to sys.path, to ensure it is cached.
This way, even if the TA path added to sys.path contains the specified library,
Python will always reference the already cached library from the Splunk Python path.
"""
lib_module = importlib.import_module(lib_name)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm wondering if this way of importing wouldn't cause issue in the future if, for any reason, we'd like to override some library that comes with Splunk

I'd be more comfortable with something that just checks if there is a possibility to import rather than importing (eg. importlib.util.find_spec)

Copy link
Author

Choose a reason for hiding this comment

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

Right now we're doing something opposite - we're overriding our libraries with the ones from Splunk. In this case if some lib does not exists, it is an error and should be found during testing

assert _is_module_from_splunk_core(
lib_module
), f"The module {lib_name} is not from Splunk core site-packages."

def _get_app_path(absolute_path: str, current_script_folder: str = "lib") -> str:
"""Returns app path."""
marker = os.path.join(os.path.sep, "etc", "apps")
start = absolute_path.rfind(marker)
if start == -1:
return None
end = absolute_path.find(current_script_folder, start)
if end == -1:
return None
end = end - 1
path = absolute_path[:end]
return path

def _remove_lib_folder(lib_name: str):
"""
List and attempt to remove any folders directly under the 'lib' directory that contain lib_name in their name.
Handles exceptions during removal, allowing the script to proceed even if errors occur.
"""

try:
app_dir = _get_app_path(os.path.abspath(__file__))
lib_dir = os.path.join(app_dir, "lib")

for entry in os.listdir(lib_dir):
entry_path = os.path.join(lib_dir, entry)
if os.path.isdir(entry_path) and lib_name in entry:
try:
shutil.rmtree(entry_path)
except Exception:
# Bypassing exceptions to ensure uninterrupted execution
pass
except Exception:
# Bypassing exceptions to ensure uninterrupted execution
pass


def handle_splunk_provided_lib(lib_name: str):
Copy link
Contributor

Choose a reason for hiding this comment

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

I do believe that should be more meaningful what it does underneath - warning ... maybe even alerting - like: delete_addon_lib

Copy link
Author

Choose a reason for hiding this comment

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

delete is not the correct word here, as deleting is just a part of the job to be done. We should find a word for both importing new one and removing leftovers

_cache_lib(lib_name)
_remove_lib_folder(lib_name)
1 change: 1 addition & 0 deletions solnlib/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,3 +219,4 @@ def get_appname_from_path(absolute_path):
pass
continue
return "-"

Loading
Loading