Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
b143237
feat: Enhance database configuration by loading credentials from envi…
junhaoliao Aug 3, 2025
6cddeda
refactor
junhaoliao Aug 3, 2025
f9d5b59
lint
junhaoliao Aug 3, 2025
489476f
fix
junhaoliao Aug 3, 2025
0e3f412
fix issues caused by refactoring
junhaoliao Aug 3, 2025
05283d5
fix: Update exception handling to catch ValueError instead of KeyError
junhaoliao Aug 5, 2025
34c5e8f
refactor: Remove unused logger initialization in general.py
junhaoliao Aug 7, 2025
842efac
reorder generated_config_file_path
junhaoliao Aug 7, 2025
05715fb
fix: Update type hint for extra_env_vars to use Dict instead of dict
junhaoliao Aug 7, 2025
87d07c6
refactor: make extra_env_vars optional
junhaoliao Aug 7, 2025
73334ed
refactor: rename generate_common_environment_variables to generate_co…
junhaoliao Aug 7, 2025
866334d
improve environment variable generation for Docker containers
junhaoliao Aug 7, 2025
dff9bef
Set default value for extra_env_vars to None for immutability instead…
junhaoliao Aug 7, 2025
00ceeb8
Rename 'load_and_validate_config_file' to 'validate_and_load_config_f…
junhaoliao Aug 7, 2025
cc9a7c7
Add get_generated_config_file_path method to dynamically generate the…
junhaoliao Aug 7, 2025
4e1c272
extract environment variable retrieval logic
junhaoliao Aug 7, 2025
7d82bec
lint
junhaoliao Aug 7, 2025
1b7c70c
update error logging in query scheduler
junhaoliao Aug 7, 2025
781bb16
Remove unused import of load_worker_config from job_orchestration.exe…
junhaoliao Aug 8, 2025
bf63d6e
Use more specific error messages in exception logs
junhaoliao Aug 8, 2025
469bc65
Remove unused import of generate_worker_config function
junhaoliao Aug 8, 2025
8654a56
Remove container_clp_config argument from generate_common_environment…
junhaoliao Aug 8, 2025
03881cc
remove unused import of logging module
junhaoliao Aug 8, 2025
0c9a035
Merge branch 'main' into db-config-file
junhaoliao Aug 8, 2025
6f8879c
rename generated_config_dir to generated_config_file
junhaoliao Aug 8, 2025
3850b2d
revert empty line before return
junhaoliao Aug 8, 2025
96735b1
Check for None explicitly - Apply suggestions from code review
junhaoliao Aug 8, 2025
365b5d6
Fix indent mistake caused by merging code - Apply suggestions from co…
junhaoliao Aug 8, 2025
c84836c
Add `dump_to_primitive_dict` method to Database, Redis, and Reducer c…
junhaoliao Aug 8, 2025
04c5c98
Merge remote-tracking branch 'junhao/db-config-file' into db-config-file
junhaoliao Aug 8, 2025
33e11ac
fix lint
junhaoliao Aug 8, 2025
6e06740
Merge remote-tracking branch 'origin/main' into db-config-file
junhaoliao Aug 14, 2025
6ef77dd
lint
junhaoliao Aug 14, 2025
36af52d
fix(package): Update `native/decompress.py` to use CLI args and env v…
junhaoliao Aug 14, 2025
d85085b
refactor(clp-py-utils): simplify sensitive information handling in CL…
junhaoliao Aug 14, 2025
e371aa8
refactor(clp-py-utils): Use os.getenv() instead of os.environ[] for m…
junhaoliao Aug 14, 2025
5fa14f8
refactor(clp-config): add environment variable constants for credentials
junhaoliao Aug 14, 2025
9eba523
refactor(clp-py-utils): move credential loading to respective classes
junhaoliao Aug 14, 2025
312445e
refactor(clp-package-utils): Replace manual environment variable read…
junhaoliao Aug 14, 2025
335f25e
lint
junhaoliao Aug 14, 2025
3cc3d44
add type annotation for _get_env_var function parameter
junhaoliao Aug 14, 2025
02f393d
Merge branch 'main' into db-config-file
junhaoliao Aug 14, 2025
8bcae09
set default values as None for optional connection fields
junhaoliao Aug 14, 2025
4a9bf99
remove unused import of os module
junhaoliao Aug 14, 2025
fc27205
Docs - Apply suggestions from code review
junhaoliao Aug 15, 2025
40e387d
Remove unused os import - Apply suggestions from code review
junhaoliao Aug 15, 2025
ed58dcc
update environment_variables generator function return type descriptions
junhaoliao Aug 15, 2025
89f001f
Rename parameter `include_clp_home` to `include_clp_home_env_var`
junhaoliao Aug 15, 2025
95b55ef
replace hardcoded env var names with constants
junhaoliao Aug 15, 2025
fa81a9d
move necessary_mounts definition
junhaoliao Aug 15, 2025
4743566
Merge branch 'main' into db-config-file
junhaoliao Aug 15, 2025
0a8de41
remove credential environment variables for reducer from start_clp.py
junhaoliao Aug 15, 2025
cbdd9fb
docs: Add missing documentation for `load_credentials_from_env` metho…
junhaoliao Aug 17, 2025
67d048a
fix(job-orchestration): update garbage collector configuration handling
junhaoliao Aug 18, 2025
f852ca4
refactor: Add CLP_GENERATED_CONFIG_FILE_NAME constant in clp_config.p…
junhaoliao Aug 18, 2025
0acb960
refactor(clp-package-utils): rename environment variable generation f…
junhaoliao Aug 18, 2025
d21efbf
shift order of CLPConfig field serialization
junhaoliao Aug 18, 2025
d446e52
refactor(scheduler): enhance error logging and component naming consi…
junhaoliao Aug 18, 2025
c0949cc
refactor(clp-package-utils): improve container environment variable h…
junhaoliao Aug 18, 2025
f723512
remove unused imports
junhaoliao Aug 18, 2025
38c75ee
lint
junhaoliao Aug 18, 2025
a11b314
Merge branch 'main' into db-config-file
junhaoliao Aug 18, 2025
4124b60
use named constant insteaad of magical string for ".clp-config.yml" -…
junhaoliao Aug 19, 2025
3fe8368
docs - Apply suggestions from code review
junhaoliao Aug 19, 2025
ce143c6
refactor(clp-py-utils): Optimize the serialization process by excludi…
junhaoliao Aug 19, 2025
e15547e
remove unused variable clp_site_packages_dir
junhaoliao Aug 19, 2025
4bba4ed
add missing mount to start_garbage_collector
junhaoliao Aug 19, 2025
6ab2f80
Update function docstrings `raise` to use consistent colon placement
junhaoliao Aug 19, 2025
cd4437d
refactor(clp-package-utils): rename environment variable generation f…
junhaoliao Aug 19, 2025
14e0219
refactor(clp-package-utils): rename `dump_shared_config` function to …
junhaoliao Aug 19, 2025
33bc068
refactor(clp-package-utils): rename and restructure configuration dum…
junhaoliao Aug 19, 2025
ae8f581
refactor(config): rename generated config file to shared config file
junhaoliao Aug 19, 2025
0e52cb1
Merge branch 'main' into db-config-file
junhaoliao Aug 19, 2025
f063bd9
lint
junhaoliao Aug 19, 2025
04f81b7
remove redundant comment - Apply suggestions from code review
junhaoliao Aug 19, 2025
34c430f
order constants - Apply suggestions from code review
junhaoliao Aug 19, 2025
31342c3
refactor(clp-package-utils): extract container config filename genera…
junhaoliao Aug 19, 2025
ca2db62
Merge branch 'main' into db-config-file
junhaoliao Aug 19, 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
135 changes: 122 additions & 13 deletions components/clp-package-utils/clp_package_utils/general.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
import typing
import uuid
from enum import auto
from typing import List, Optional, Tuple
from typing import Dict, List, Optional, Tuple
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Unify typing style (avoid mixing typing.Optional with Optional).

You import Optional, List, etc., but still use typing.Optional elsewhere. Prefer one style for consistency; since you already import from typing, use Optional consistently.

Apply this refactor in the class where it appears:

 class CLPDockerMounts:
     def __init__(self, clp_home: pathlib.Path, docker_clp_home: pathlib.Path):
-        self.input_logs_dir: typing.Optional[DockerMount] = None
+        self.input_logs_dir: Optional[DockerMount] = None
         self.clp_home: typing.Optional[DockerMount] = DockerMount(
             DockerMountType.BIND, clp_home, docker_clp_home
         )
-        self.data_dir: typing.Optional[DockerMount] = None
-        self.logs_dir: typing.Optional[DockerMount] = None
-        self.archives_output_dir: typing.Optional[DockerMount] = None
-        self.stream_output_dir: typing.Optional[DockerMount] = None
-        self.aws_config_dir: typing.Optional[DockerMount] = None
+        self.data_dir: Optional[DockerMount] = None
+        self.logs_dir: Optional[DockerMount] = None
+        self.archives_output_dir: Optional[DockerMount] = None
+        self.stream_output_dir: Optional[DockerMount] = None
+        self.aws_config_dir: Optional[DockerMount] = None
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from typing import Dict, List, Optional, Tuple
class CLPDockerMounts:
def __init__(self, clp_home: pathlib.Path, docker_clp_home: pathlib.Path):
self.input_logs_dir: Optional[DockerMount] = None
self.clp_home: typing.Optional[DockerMount] = DockerMount(
DockerMountType.BIND, clp_home, docker_clp_home
)
self.data_dir: Optional[DockerMount] = None
self.logs_dir: Optional[DockerMount] = None
self.archives_output_dir: Optional[DockerMount] = None
self.stream_output_dir: Optional[DockerMount] = None
self.aws_config_dir: Optional[DockerMount] = None
🤖 Prompt for AI Agents
In components/clp-package-utils/clp_package_utils/general.py around line 12,
unify typing style by replacing any uses of typing.Optional in the class (and
elsewhere in this file) with the already-imported Optional; ensure Optional is
present in the from typing import ... list (add it if missing), remove any
redundant direct imports of typing if they are only used for Optional, and run a
quick search/replace in the file to convert typing.List/typing.Dict/etc. to the
imported aliases (List, Dict) for consistent style.


import yaml
from clp_py_utils.clp_config import (
CLP_DEFAULT_CREDENTIALS_FILE_PATH,
CLP_SHARED_CONFIG_FILENAME,
CLPConfig,
DB_COMPONENT_NAME,
QueryEngine,
Expand Down Expand Up @@ -95,6 +96,7 @@ def __init__(self, clp_home: pathlib.Path, docker_clp_home: pathlib.Path):
self.archives_output_dir: typing.Optional[DockerMount] = None
self.stream_output_dir: typing.Optional[DockerMount] = None
self.aws_config_dir: typing.Optional[DockerMount] = None
self.generated_config_file: typing.Optional[DockerMount] = None
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

LGTM: generated_config_file mount slot added

Good addition. Consider marking the file mount read-only when created to prevent accidental in-container edits.

🤖 Prompt for AI Agents
In components/clp-package-utils/clp_package_utils/general.py around line 97, the
new generated_config_file mount should be created as read-only to prevent
accidental in-container edits; when assigning/creating the DockerMount for
self.generated_config_file, set the mount mode/read_only flag to read-only
(e.g., mode or read_only parameter) so the resulting mount is mounted as ro
rather than writable.


Comment on lines +99 to 100
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Nit: align typing style (Optional over typing.Optional)

You already import Optional; keep usage consistent.

-        self.generated_config_file: typing.Optional[DockerMount] = None
+        self.generated_config_file: Optional[DockerMount] = None
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
self.generated_config_file: typing.Optional[DockerMount] = None
self.generated_config_file: Optional[DockerMount] = None
🤖 Prompt for AI Agents
In components/clp-package-utils/clp_package_utils/general.py around lines 98 to
99, the typing annotation uses typing.Optional[DockerMount] while Optional is
already imported; change typing.Optional[DockerMount] to Optional[DockerMount]
to keep typing style consistent across the file.


def _validate_data_directory(data_dir: pathlib.Path, component_name: str) -> None:
Expand Down Expand Up @@ -285,6 +287,18 @@ def generate_container_config(
container_clp_config.stream_output.get_directory(),
)

if not is_path_already_mounted(
clp_home,
CONTAINER_CLP_HOME,
clp_config.get_shared_config_file_path(),
container_clp_config.get_shared_config_file_path(),
):
docker_mounts.generated_config_file = DockerMount(
DockerMountType.BIND,
clp_config.get_shared_config_file_path(),
container_clp_config.get_shared_config_file_path(),
)

Comment on lines +290 to +301
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Mark the shared config file mount read-only

The shared config should not be mutated in-container. Make the bind mount ro.

-        docker_mounts.generated_config_file = DockerMount(
-            DockerMountType.BIND,
-            clp_config.get_shared_config_file_path(),
-            container_clp_config.get_shared_config_file_path(),
-        )
+        docker_mounts.generated_config_file = DockerMount(
+            DockerMountType.BIND,
+            clp_config.get_shared_config_file_path(),
+            container_clp_config.get_shared_config_file_path(),
+            True,
+        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if not is_path_already_mounted(
clp_home,
CONTAINER_CLP_HOME,
clp_config.get_shared_config_file_path(),
container_clp_config.get_shared_config_file_path(),
):
docker_mounts.generated_config_file = DockerMount(
DockerMountType.BIND,
clp_config.get_shared_config_file_path(),
container_clp_config.get_shared_config_file_path(),
)
if not is_path_already_mounted(
clp_home,
CONTAINER_CLP_HOME,
clp_config.get_shared_config_file_path(),
container_clp_config.get_shared_config_file_path(),
):
docker_mounts.generated_config_file = DockerMount(
DockerMountType.BIND,
clp_config.get_shared_config_file_path(),
container_clp_config.get_shared_config_file_path(),
True,
)
🤖 Prompt for AI Agents
In components/clp-package-utils/clp_package_utils/general.py around lines 290 to
301, the shared config file is currently bind-mounted into the container as
writable; change the DockerMount used for docker_mounts.generated_config_file to
be read-only so the in-container process cannot mutate it. Modify the
DockerMount construction to include the read-only option (for example by adding
mode='ro' or readonly=True or the equivalent parameter your DockerMount class
expects) when creating the bind mount, ensuring the source and target paths
remain the same.

# Only create the mount if the directory exists
if clp_config.aws_config_directory is not None:
container_clp_config.aws_config_directory = CONTAINER_AWS_CONFIG_DIRECTORY
Expand All @@ -308,33 +322,57 @@ def generate_worker_config(clp_config: CLPConfig) -> WorkerConfig:
return worker_config


def get_container_config_filename(container_name: str) -> str:
return f".{container_name}-config.yml"


def dump_container_config(
container_clp_config: CLPConfig, clp_config: CLPConfig, container_name: str
) -> Tuple[pathlib.Path, pathlib.Path]:
container_clp_config: CLPConfig, clp_config: CLPConfig, config_filename: str
):
"""
Writes the given config to the logs directory so that it's accessible in the container.
Writes the given container config to the logs directory, so that it's accessible in the
container.

:param container_clp_config: The config to write.
:param clp_config: The corresponding config on the host (used to determine the logs directory).
:param container_name:
:param config_filename:
:return: The path to the config file in the container and on the host.
"""
container_config_filename = f".{container_name}-config.yml"
config_file_path_on_host = clp_config.logs_directory / container_config_filename
config_file_path_on_container = container_clp_config.logs_directory / container_config_filename
config_file_path_on_host = clp_config.logs_directory / config_filename
config_file_path_on_container = container_clp_config.logs_directory / config_filename
with open(config_file_path_on_host, "w") as f:
yaml.safe_dump(container_clp_config.dump_to_primitive_dict(), f)

return config_file_path_on_container, config_file_path_on_host


def dump_shared_container_config(
container_clp_config: CLPConfig, clp_config: CLPConfig
) -> Tuple[pathlib.Path, pathlib.Path]:
"""
Dumps the given container config to `CLP_SHARED_CONFIG_FILENAME` in the logs directory, so that
it's accessible in the container.

:param container_clp_config:
:param clp_config:
"""
return dump_container_config(container_clp_config, clp_config, CLP_SHARED_CONFIG_FILENAME)


def generate_container_start_cmd(
container_name: str, container_mounts: List[Optional[DockerMount]], container_image: str
container_name: str,
container_mounts: List[Optional[DockerMount]],
container_image: str,
extra_env_vars: Optional[Dict[str, str]] = None,
) -> List[str]:
"""
Generates the command to start a container with the given mounts and name.
Generates the command to start a container with the given mounts, environment variables, and
name.

:param container_name:
:param container_mounts:
:param container_image:
:param extra_env_vars: Environment variables to set on top of the predefined ones.
:return: The command.
"""
clp_site_packages_dir = CONTAINER_CLP_HOME / "lib" / "python3" / "site-packages"
Expand All @@ -350,6 +388,12 @@ def generate_container_start_cmd(
"--name", container_name,
"--log-driver", "local"
]
env_vars = {
"PYTHONPATH": clp_site_packages_dir,
Copy link
Member Author

Choose a reason for hiding this comment

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

the above "-e", f"PYTHONPATH={clp_site_packages_dir}", line was not removed, as a mistake possibly during branch syncs with main, which causes the environment variable to be defined twice. The duplication isn't harmful in the correctness though.

@coderabbitai create an issue to track the removal of the deplicate

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Create GitHub issue to track duplicate PYTHONPATH removal
gh issue create \
  --title "Remove duplicate PYTHONPATH environment variable in generate_container_start_cmd" \
  --body "## Description

In \`components/clp-package-utils/clp_package_utils/general.py\`, the \`generate_container_start_cmd\` function defines the PYTHONPATH environment variable twice:

1. Directly in the \`container_start_cmd\` array: \`\"-e\", f\"PYTHONPATH={clp_site_packages_dir}\"\`
2. In the \`env_vars\` dictionary, which is then iterated to add environment variables to the command

This duplication occurred during branch syncs and should be cleaned up. While not harmful to correctness, only one definition is necessary.

## References

- PR: https://github.com/y-scope/clp/pull/1152
- Comment: https://github.com/y-scope/clp/pull/1152#discussion_r2468365115
- Reported by: @junhaoliao" \
  --assignee junhaoliao \
  --repo y-scope/clp

Length of output: 42


@junhaoliao, I've created issue #1513 to track the removal of the duplicate PYTHONPATH environment variable definition in generate_container_start_cmd. The issue has been assigned to you.


🧬 Code Graph Analysis Results

[clp-package-utils/general.py] Summary: generate_container_start_cmd

  • Description: Creates the docker run command to start a container with mounts, environment variables, and a name.
  • Parameters:
    • container_name: str
    • container_mounts: List[Optional[DockerMount]]
    • container_image: str
    • extra_env_vars: Optional[Dict[str, str]]
  • Returns: List[str] representing the docker run command.
  • Key behavior:
    • Defines clp_site_packages_dir as CONTAINER_CLP_HOME / "lib" / "python3" / "site-packages".
    • Includes an environment variable "-e PYTHONPATH={clp_site_packages_dir}" unconditionally.
    • Merges extra_env_vars (if provided) into the command as additional -e KEY=VALUE pairs.
    • Appends mount arguments for non-None mounts.
  • Important details:
    • The command explicitly sets PYTHONPATH in the container startup command, which may duplicate with other environment-variable provisioning (e.g., a separate list of environment vars that also sets PYTHONPATH).
    • This function is central to how the container’s environment is prepared and could interact with other env var provisioning logic.

[clp-package-utils/general.py] Summary: get_common_env_vars_list

  • Description: Returns a list of common environment variables for Docker containers.
  • Parameters:
    • include_clp_home_env_var: bool (default True)
  • Returns: List[str] where each item is in the form KEY=VALUE.
  • Key behavior:
    • Always includes PYTHONPATH={CONTAINER_CLP_HOME}/lib/python3/site-packages.
    • If include_clp_home_env_var is True, also includes CLP_HOME={CONTAINER_CLP_HOME}.
  • Important details:
    • This list provides a standard set of env vars for containers.
    • Note: PYTHONPATH is defined here, which can interact with the PYTHONPATH set in generate_container_start_cmd (potential duplication if both methods are used).

**(extra_env_vars if extra_env_vars is not None else {}),
}
for key, value in env_vars.items():
container_start_cmd.extend(["-e", f"{key}={value}"])
for mount in container_mounts:
if mount:
container_start_cmd.append("--mount")
Expand Down Expand Up @@ -428,21 +472,21 @@ def validate_and_load_db_credentials_file(
clp_config: CLPConfig, clp_home: pathlib.Path, generate_default_file: bool
):
validate_credentials_file_path(clp_config, clp_home, generate_default_file)
clp_config.load_database_credentials_from_file()
clp_config.database.load_credentials_from_file(clp_config.credentials_file_path)


def validate_and_load_queue_credentials_file(
clp_config: CLPConfig, clp_home: pathlib.Path, generate_default_file: bool
):
validate_credentials_file_path(clp_config, clp_home, generate_default_file)
clp_config.load_queue_credentials_from_file()
clp_config.queue.load_credentials_from_file(clp_config.credentials_file_path)


def validate_and_load_redis_credentials_file(
clp_config: CLPConfig, clp_home: pathlib.Path, generate_default_file: bool
):
validate_credentials_file_path(clp_config, clp_home, generate_default_file)
clp_config.load_redis_credentials_from_file()
clp_config.redis.load_credentials_from_file(clp_config.credentials_file_path)


def validate_db_config(clp_config: CLPConfig, data_dir: pathlib.Path, logs_dir: pathlib.Path):
Expand Down Expand Up @@ -599,3 +643,68 @@ def is_retention_period_configured(clp_config: CLPConfig) -> bool:
return True

return False


def get_common_env_vars_list(
include_clp_home_env_var=True,
) -> List[str]:
"""
:param include_clp_home_env_var:
:return: A list of common environment variables for Docker containers, in the format
"KEY=VALUE".
"""
clp_site_packages_dir = CONTAINER_CLP_HOME / "lib" / "python3" / "site-packages"
env_vars = [f"PYTHONPATH={clp_site_packages_dir}"]

if include_clp_home_env_var:
env_vars.append(f"CLP_HOME={CONTAINER_CLP_HOME}")

return env_vars


def get_credential_env_vars_list(
container_clp_config: CLPConfig,
include_db_credentials=False,
include_queue_credentials=False,
include_redis_credentials=False,
) -> List[str]:
"""
:param container_clp_config:
:param include_db_credentials:
:param include_queue_credentials:
:param include_redis_credentials:
:return: A list of credential environment variables for Docker containers, in the format
"KEY=VALUE".
"""
env_vars = []

if include_db_credentials:
env_vars.append(f"CLP_DB_USER={container_clp_config.database.username}")
env_vars.append(f"CLP_DB_PASS={container_clp_config.database.password}")

if include_queue_credentials:
env_vars.append(f"CLP_QUEUE_USER={container_clp_config.queue.username}")
env_vars.append(f"CLP_QUEUE_PASS={container_clp_config.queue.password}")

if include_redis_credentials:
env_vars.append(f"CLP_REDIS_PASS={container_clp_config.redis.password}")

return env_vars


def get_celery_connection_env_vars_list(container_clp_config: CLPConfig) -> List[str]:
"""
:param container_clp_config:
:return: A list of Celery connection environment variables for Docker containers, in the format
"KEY=VALUE".
"""
env_vars = [
f"BROKER_URL=amqp://"
f"{container_clp_config.queue.username}:{container_clp_config.queue.password}@"
f"{container_clp_config.queue.host}:{container_clp_config.queue.port}",
f"RESULT_BACKEND=redis://default:{container_clp_config.redis.password}@"
f"{container_clp_config.redis.host}:{container_clp_config.redis.port}/"
f"{container_clp_config.redis.query_backend_database}",
Comment on lines +701 to +707
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

URL-encode credentials in Celery connection strings

Credentials containing special characters (e.g., @, :, /) will break the connection URLs.

Apply URL encoding to credentials before embedding them in connection strings:

+from urllib.parse import quote

 def get_celery_connection_env_vars_list(container_clp_config: CLPConfig) -> List[str]:
     """
     :param container_clp_config:
     :return: A list of Celery connection environment variables for Docker containers, in the format
     "KEY=VALUE".
     """
+    queue_user = quote(container_clp_config.queue.username, safe='')
+    queue_pass = quote(container_clp_config.queue.password, safe='')
+    redis_pass = quote(container_clp_config.redis.password, safe='')
     env_vars = [
         f"BROKER_URL=amqp://"
-        f"{container_clp_config.queue.username}:{container_clp_config.queue.password}@"
+        f"{queue_user}:{queue_pass}@"
         f"{container_clp_config.queue.host}:{container_clp_config.queue.port}",
-        f"RESULT_BACKEND=redis://default:{container_clp_config.redis.password}@"
+        f"RESULT_BACKEND=redis://default:{redis_pass}@"
         f"{container_clp_config.redis.host}:{container_clp_config.redis.port}/"
         f"{container_clp_config.redis.query_backend_database}",
     ]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
env_vars = [
f"BROKER_URL=amqp://"
f"{container_clp_config.queue.username}:{container_clp_config.queue.password}@"
f"{container_clp_config.queue.host}:{container_clp_config.queue.port}",
f"RESULT_BACKEND=redis://default:{container_clp_config.redis.password}@"
f"{container_clp_config.redis.host}:{container_clp_config.redis.port}/"
f"{container_clp_config.redis.query_backend_database}",
from urllib.parse import quote
def get_celery_connection_env_vars_list(container_clp_config: CLPConfig) -> List[str]:
"""
:param container_clp_config:
:return: A list of Celery connection environment variables for Docker containers, in the format
"KEY=VALUE".
"""
queue_user = quote(container_clp_config.queue.username, safe='')
queue_pass = quote(container_clp_config.queue.password, safe='')
redis_pass = quote(container_clp_config.redis.password, safe='')
env_vars = [
f"BROKER_URL=amqp://"
f"{queue_user}:{queue_pass}@"
f"{container_clp_config.queue.host}:{container_clp_config.queue.port}",
f"RESULT_BACKEND=redis://default:{redis_pass}@"
f"{container_clp_config.redis.host}:{container_clp_config.redis.port}/"
f"{container_clp_config.redis.query_backend_database}",
]
return env_vars
🤖 Prompt for AI Agents
In components/clp-package-utils/clp_package_utils/general.py around lines 695 to
701, the Celery BROKER_URL and RESULT_BACKEND strings embed raw usernames and
passwords which will break when credentials contain special characters;
URL-encode the queue username and password and the redis password (using
urllib.parse.quote_plus or quote) before interpolating them into the connection
URLs so special characters are percent-encoded, and update the env_vars
construction to use the encoded credential variables.

]

return env_vars
Comment on lines +695 to +710
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Celery env: parameterise Redis DB and URL-encode credentials

This helper hardcodes the query DB and embeds raw credentials. Parameterise the DB index and encode user/pass to avoid URL parsing issues.

-def get_celery_connection_env_vars_list(container_clp_config: CLPConfig) -> List[str]:
+def get_celery_connection_env_vars_list(
+    container_clp_config: CLPConfig,
+    redis_database: Optional[int] = None,
+) -> List[str]:
     """
     :param container_clp_config:
     :return: A list of Celery connection environment variables for Docker containers, in the format
     "KEY=VALUE".
     """
-    env_vars = [
-        f"BROKER_URL=amqp://"
-        f"{container_clp_config.queue.username}:{container_clp_config.queue.password}@"
-        f"{container_clp_config.queue.host}:{container_clp_config.queue.port}",
-        f"RESULT_BACKEND=redis://default:{container_clp_config.redis.password}@"
-        f"{container_clp_config.redis.host}:{container_clp_config.redis.port}/"
-        f"{container_clp_config.redis.query_backend_database}",
-    ]
+    from urllib.parse import quote
+    q_user = "" if container_clp_config.queue.username is None else quote(container_clp_config.queue.username, safe="")
+    q_pass = "" if container_clp_config.queue.password is None else quote(container_clp_config.queue.password, safe="")
+    r_pass = container_clp_config.redis.password
+    redis_db = (
+        container_clp_config.redis.query_backend_database
+        if redis_database is None
+        else redis_database
+    )
+    broker = (
+        f"amqp://{q_user}:{q_pass}@"
+        f"{container_clp_config.queue.host}:{container_clp_config.queue.port}"
+    )
+    if r_pass:
+        backend = (
+            "redis://default:"
+            f"{quote(r_pass, safe='')}@"
+            f"{container_clp_config.redis.host}:{container_clp_config.redis.port}/{redis_db}"
+        )
+    else:
+        backend = (
+            f"redis://{container_clp_config.redis.host}:{container_clp_config.redis.port}/{redis_db}"
+        )
+    env_vars = [f"BROKER_URL={broker}", f"RESULT_BACKEND={backend}"]
 
     return env_vars
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def get_celery_connection_env_vars_list(container_clp_config: CLPConfig) -> List[str]:
"""
:param container_clp_config:
:return: A list of Celery connection environment variables for Docker containers, in the format
"KEY=VALUE".
"""
env_vars = [
f"BROKER_URL=amqp://"
f"{container_clp_config.queue.username}:{container_clp_config.queue.password}@"
f"{container_clp_config.queue.host}:{container_clp_config.queue.port}",
f"RESULT_BACKEND=redis://default:{container_clp_config.redis.password}@"
f"{container_clp_config.redis.host}:{container_clp_config.redis.port}/"
f"{container_clp_config.redis.query_backend_database}",
]
return env_vars
def get_celery_connection_env_vars_list(
container_clp_config: CLPConfig,
redis_database: Optional[int] = None,
) -> List[str]:
"""
:param container_clp_config:
:return: A list of Celery connection environment variables for Docker containers, in the format
"KEY=VALUE".
"""
from urllib.parse import quote
q_user = "" if container_clp_config.queue.username is None else quote(
container_clp_config.queue.username, safe=""
)
q_pass = "" if container_clp_config.queue.password is None else quote(
container_clp_config.queue.password, safe=""
)
r_pass = container_clp_config.redis.password
redis_db = (
container_clp_config.redis.query_backend_database
if redis_database is None
else redis_database
)
broker = (
f"amqp://{q_user}:{q_pass}@"
f"{container_clp_config.queue.host}:{container_clp_config.queue.port}"
)
if r_pass:
backend = (
"redis://default:"
f"{quote(r_pass, safe='')}@"
f"{container_clp_config.redis.host}:{container_clp_config.redis.port}/{redis_db}"
)
else:
backend = (
f"redis://{container_clp_config.redis.host}:{container_clp_config.redis.port}/{redis_db}"
)
env_vars = [f"BROKER_URL={broker}", f"RESULT_BACKEND={backend}"]
return env_vars
🤖 Prompt for AI Agents
In components/clp-package-utils/clp_package_utils/general.py around lines 691 to
706, the Celery env helper currently hardcodes the Redis DB index and injects
raw credentials into URLs; update it to parameterise the Redis DB index (use the
existing config field, e.g., redis.query_backend_database or rename to redis.db
if needed) and URL-encode both broker and redis username/password to avoid URL
parsing issues. Import and use urllib.parse.quote_plus to encode
container_clp_config.queue.username/container_clp_config.queue.password and
container_clp_config.redis.username/container_clp_config.redis.password, then
build BROKER_URL and RESULT_BACKEND using the encoded credentials and the config
DB index instead of a hardcoded value; return the env_vars list as before.

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from typing import Final, List, Optional

from clp_py_utils.clp_config import (
CLP_DB_PASS_ENV_VAR_NAME,
CLP_DB_USER_ENV_VAR_NAME,
CLP_DEFAULT_DATASET_NAME,
StorageEngine,
StorageType,
Expand All @@ -20,6 +22,7 @@
generate_container_name,
generate_container_start_cmd,
get_clp_home,
get_container_config_filename,
load_config_file,
validate_and_load_db_credentials_file,
validate_dataset_name,
Expand Down Expand Up @@ -212,16 +215,20 @@ def main(argv: List[str]) -> int:

container_clp_config, mounts = generate_container_config(clp_config, clp_home)
generated_config_path_on_container, generated_config_path_on_host = dump_container_config(
container_clp_config, clp_config, container_name
container_clp_config, clp_config, get_container_config_filename(container_name)
)

necessary_mounts: List[Optional[DockerMount]] = [
mounts.clp_home,
mounts.logs_dir,
mounts.archives_output_dir,
]
extra_env_vars = {
CLP_DB_USER_ENV_VAR_NAME: clp_config.database.username,
CLP_DB_PASS_ENV_VAR_NAME: clp_config.database.password,
}
container_start_cmd: List[str] = generate_container_start_cmd(
container_name, necessary_mounts, clp_config.execution_container
container_name, necessary_mounts, clp_config.execution_container, extra_env_vars
)
Comment on lines +226 to 232
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Do not pass secrets on the Docker command line; provide values via subprocess env

Passing -e KEY=VALUE exposes creds via process args and logs. Pass only the keys to the container CLI and inject the values via subprocess.run env.

-    extra_env_vars = {
-        CLP_DB_USER_ENV_VAR_NAME: clp_config.database.username,
-        CLP_DB_PASS_ENV_VAR_NAME: clp_config.database.password,
-    }
-    container_start_cmd: List[str] = generate_container_start_cmd(
-        container_name, necessary_mounts, clp_config.execution_container, extra_env_vars
-    )
+    extra_env_vars = {
+        CLP_DB_USER_ENV_VAR_NAME: clp_config.database.username,
+        CLP_DB_PASS_ENV_VAR_NAME: clp_config.database.password,
+    }
+    # Provide only names to the docker CLI; inject values via subprocess env.
+    container_start_cmd: List[str] = generate_container_start_cmd(
+        container_name,
+        necessary_mounts,
+        clp_config.execution_container,
+        list(extra_env_vars.keys()),
+    )

Apply outside-range updates to complete the change:

# At top-level imports
import os  # add

# Where the command is executed
proc = subprocess.run(cmd, env={**os.environ, **extra_env_vars})


# fmt: off
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from typing import List, Optional

from clp_py_utils.clp_config import (
CLP_DB_PASS_ENV_VAR_NAME,
CLP_DB_USER_ENV_VAR_NAME,
CLP_DEFAULT_DATASET_NAME,
StorageEngine,
)
Expand All @@ -20,6 +22,7 @@
generate_container_name,
generate_container_start_cmd,
get_clp_home,
get_container_config_filename,
JobType,
load_config_file,
validate_and_load_db_credentials_file,
Expand Down Expand Up @@ -202,7 +205,7 @@ def main(argv):

container_clp_config, mounts = generate_container_config(clp_config, clp_home)
generated_config_path_on_container, generated_config_path_on_host = dump_container_config(
container_clp_config, clp_config, container_name
container_clp_config, clp_config, get_container_config_filename(container_name)
)

necessary_mounts = [mounts.clp_home, mounts.data_dir, mounts.logs_dir]
Expand All @@ -222,8 +225,12 @@ def main(argv):

_generate_logs_list(clp_config.logs_input.type, container_logs_list_path, parsed_args)

extra_env_vars = {
CLP_DB_USER_ENV_VAR_NAME: clp_config.database.username,
CLP_DB_PASS_ENV_VAR_NAME: clp_config.database.password,
}
container_start_cmd = generate_container_start_cmd(
container_name, necessary_mounts, clp_config.execution_container
container_name, necessary_mounts, clp_config.execution_container, extra_env_vars
)
Comment on lines +228 to 234
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Stop leaking DB credentials on the Docker CLI; pass values via subprocess env

extra_env_vars currently flows into the docker argv as -e KEY=VALUE, exposing secrets via ps and logs. Emit only -e KEY and inject the values via subprocess env.

Action:

  • Update generate_container_start_cmd to accept a list of env var names and emit -e KEY (no value).
  • Pass only the keys here and provide the values to subprocess.run(env=...).

Apply within this hunk:

-    container_start_cmd = generate_container_start_cmd(
-        container_name, necessary_mounts, clp_config.execution_container, extra_env_vars
-    )
+    # NOTE: Requires general.generate_container_start_cmd to emit '-e KEY' (no '=VALUE')
+    container_start_cmd = generate_container_start_cmd(
+        container_name,
+        necessary_mounts,
+        clp_config.execution_container,
+        list(extra_env_vars.keys()),
+    )

And update the subprocess invocation to inject secrets via env (outside this hunk):

# at top of file
import os
-    subprocess.run(cmd, check=True)
+    subprocess.run(cmd, check=True, env={**os.environ, **extra_env_vars})

Run to locate other CLI injections that still embed secrets:


🏁 Script executed:

#!/bin/bash
rg -n -C2 -P '\bgenerate_container_start_cmd\s*\(' --glob '!**/venv/**'
rg -n -C2 -P 'append_docker_options\(.+,\s*env_vars\)' --glob '!**/venv/**'

Length of output: 9124


Stop leaking DB credentials via Docker CLI flags

Exposing secrets with “-e KEY=VALUE” leaks credentials in process listings and logs. To secure this:

• Refactor generate_container_start_cmd (in components/clp-package-utils/clp_package_utils/general.py) to
– accept env_var_names: List[str] instead of a name-value dict
– emit only ["-e", KEY] for each name
• Refactor append_docker_options similarly to take names only
• In every script that currently passes extra_env_vars as a dict, change to pass only the keys and inject the values via subprocess.run(env=…):
– components/clp-package-utils/clp_package_utils/scripts/search.py (lines 122–126)
– components/clp-package-utils/clp_package_utils/scripts/compress.py (229–233)
– components/clp-package-utils/clp_package_utils/scripts/decompress.py (132–134, 206–210)
– components/clp-package-utils/clp_package_utils/scripts/archive_manager.py (227–231)
– all append_docker_options(...) calls in start_clp.py (multiple locations)

Add at top of each affected file:

import os

Example diff for compress.py:

-    extra_env_vars = {
-        CLP_DB_USER_ENV_VAR_NAME: clp_config.database.username,
-        CLP_DB_PASS_ENV_VAR_NAME: clp_config.database.password,
-    }
-    container_start_cmd = generate_container_start_cmd(
-        container_name, necessary_mounts, clp_config.execution_container, extra_env_vars
-    )
+    extra_env_vars = {
+        CLP_DB_USER_ENV_VAR_NAME: clp_config.database.username,
+        CLP_DB_PASS_ENV_VAR_NAME: clp_config.database.password,
+    }
+    # Emit only “-e KEY” and inject values via env
+    container_start_cmd = generate_container_start_cmd(
+        container_name,
+        necessary_mounts,
+        clp_config.execution_container,
+        list(extra_env_vars.keys()),
+    )
@@ -240,7 +245,9 @@ def run_compress(...):
-    subprocess.run(container_start_cmd, check=True)
+    subprocess.run(
+        container_start_cmd,
+        check=True,
+        env={**os.environ, **extra_env_vars},
+    )
🤖 Prompt for AI Agents
In components/clp-package-utils/clp_package_utils/scripts/compress.py around
lines 227 to 233, the code currently builds extra_env_vars as a dict containing
DB credentials and passes it to generate_container_start_cmd, which leaks
secrets via Docker CLI; update the call to pass only the environment variable
NAMES (e.g., a list of CLP_DB_USER_ENV_VAR_NAME and CLP_DB_PASS_ENV_VAR_NAME)
instead of a name->value dict, ensure generate_container_start_cmd and
append_docker_options in
components/clp-package-utils/clp_package_utils/general.py are refactored to
accept List[str] and emit only ["-e", KEY] for each key, and change the
subprocess invocation that launches Docker to inject the actual secret values
via subprocess.run(..., env=os.environ_with_secrets) (add import os at top of
file) so values are set in the child process environment rather than on the CLI.

compress_cmd = _generate_compress_cmd(
parsed_args, dataset, generated_config_path_on_container, logs_list_path_on_container
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
generate_container_name,
generate_container_start_cmd,
get_clp_home,
get_container_config_filename,
load_config_file,
validate_and_load_db_credentials_file,
validate_dataset_name,
Expand Down Expand Up @@ -124,7 +125,7 @@ def main(argv: List[str]) -> int:

container_clp_config, mounts = generate_container_config(clp_config, clp_home)
generated_config_path_on_container, generated_config_path_on_host = dump_container_config(
container_clp_config, clp_config, container_name
container_clp_config, clp_config, get_container_config_filename(container_name)
)

necessary_mounts = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from typing import Optional

from clp_py_utils.clp_config import (
CLP_DB_PASS_ENV_VAR_NAME,
CLP_DB_USER_ENV_VAR_NAME,
CLP_DEFAULT_DATASET_NAME,
CLPConfig,
StorageEngine,
Expand All @@ -24,6 +26,7 @@
generate_container_name,
generate_container_start_cmd,
get_clp_home,
get_container_config_filename,
JobType,
load_config_file,
validate_and_load_db_credentials_file,
Expand Down Expand Up @@ -99,7 +102,7 @@ def handle_extract_file_cmd(
container_name = generate_container_name(str(JobType.FILE_EXTRACTION))
container_clp_config, mounts = generate_container_config(clp_config, clp_home)
generated_config_path_on_container, generated_config_path_on_host = dump_container_config(
container_clp_config, clp_config, container_name
container_clp_config, clp_config, get_container_config_filename(container_name)
)

# Set up mounts
Expand All @@ -122,8 +125,13 @@ def handle_extract_file_cmd(
container_paths_to_extract_file_path,
)
)

extra_env_vars = {
CLP_DB_USER_ENV_VAR_NAME: clp_config.database.username,
CLP_DB_PASS_ENV_VAR_NAME: clp_config.database.password,
}
Comment on lines +129 to +132
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

DRY: factor out DB env var assembly

The extra_env_vars construction is duplicated. Extract a tiny helper to keep it consistent and reduce future drift.

Example:

def _build_db_env(clp_config: CLPConfig) -> Dict[str, str]:
    return {
        CLP_DB_USER_ENV_VAR_NAME: clp_config.database.username,
        CLP_DB_PASS_ENV_VAR_NAME: clp_config.database.password,
    }

Then:

extra_env_vars = _build_db_env(clp_config)

Also applies to: 202-205

🤖 Prompt for AI Agents
In components/clp-package-utils/clp_package_utils/scripts/decompress.py around
lines 126-129 (and also apply the same change at lines ~202-205), the DB env var
dict is duplicated; extract a small helper function _build_db_env(clp_config:
CLPConfig) that returns the dict mapping CLP_DB_USER_ENV_VAR_NAME and
CLP_DB_PASS_ENV_VAR_NAME to clp_config.database.username/password, then replace
both inline constructions with a call to _build_db_env(clp_config) so both sites
reuse the same logic and avoid drift.

container_start_cmd = generate_container_start_cmd(
container_name, necessary_mounts, clp_config.execution_container
container_name, necessary_mounts, clp_config.execution_container, extra_env_vars
)
Comment on lines +129 to 135
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Do not print DB creds in docker argv; pass via env

Emit only the variable names on the docker command line and inject actual values via subprocess env.

Apply:

-    container_start_cmd = generate_container_start_cmd(
-        container_name, necessary_mounts, clp_config.execution_container, extra_env_vars
-    )
+    # NOTE: Requires general.generate_container_start_cmd to emit '-e KEY'
+    container_start_cmd = generate_container_start_cmd(
+        container_name,
+        necessary_mounts,
+        clp_config.execution_container,
+        list(extra_env_vars.keys()),
+    )

And later (outside this hunk):

import os  # at top if missing
# ...
subprocess.run(cmd, check=True, env={**os.environ, **extra_env_vars})


# fmt: off
Expand Down Expand Up @@ -191,11 +199,15 @@ def handle_extract_stream_cmd(
container_name = generate_container_name(str(JobType.IR_EXTRACTION))
container_clp_config, mounts = generate_container_config(clp_config, clp_home)
generated_config_path_on_container, generated_config_path_on_host = dump_container_config(
container_clp_config, clp_config, container_name
container_clp_config, clp_config, get_container_config_filename(container_name)
)
necessary_mounts = [mounts.clp_home, mounts.logs_dir]
extra_env_vars = {
CLP_DB_USER_ENV_VAR_NAME: clp_config.database.username,
CLP_DB_PASS_ENV_VAR_NAME: clp_config.database.password,
}
container_start_cmd = generate_container_start_cmd(
container_name, necessary_mounts, clp_config.execution_container
container_name, necessary_mounts, clp_config.execution_container, extra_env_vars
)
Comment on lines +205 to 211
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Apply the same secret-handling fix to the stream extraction path

Mirror the recommended approach here as well: pass only names to Docker and set values in subprocess env to avoid leaking credentials via argv.

Apply this localized change (after updating the builder as described above):

-    container_start_cmd = generate_container_start_cmd(
-        container_name, necessary_mounts, clp_config.execution_container, extra_env_vars
-    )
+    container_start_cmd = generate_container_start_cmd(
+        container_name,
+        necessary_mounts,
+        clp_config.execution_container,
+        env_var_names=list(extra_env_vars.keys()),
+    )

And ensure:

env = {**os.environ, **extra_env_vars}
subprocess.run(cmd, check=True, env=env)


# fmt: off
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ def main(argv: List[str]) -> int:
config_file_path, default_config_file_path, clp_home
)
clp_config.validate_logs_dir()
clp_config.database.load_credentials_from_env()
except:
logger.exception("Failed to load config.")
return -1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ def main(argv):
clp_config = load_config_file(config_file_path, default_config_file_path, clp_home)
clp_config.validate_logs_input_config()
clp_config.validate_logs_dir()
clp_config.database.load_credentials_from_env()
except:
logger.exception("Failed to load config.")
return -1
Expand Down
Loading