Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(config): add descriptions to all configuration variables and tests #682

Closed
Show file tree
Hide file tree
Changes from all 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
150 changes: 93 additions & 57 deletions packages/opal-client/opal_client/config.py

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions packages/opal-client/opal_client/tests/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

import pytest
from opal_client.config import opal_client_config
from opal_common.confi.types import ConfiEntry

def test_client_config_descriptions():
missing_descriptions = []

for key, entry in opal_client_config.entries.items():
if isinstance(entry, ConfiEntry) and not entry.description:
missing_descriptions.append(key)

assert not missing_descriptions, f"The following config variables are missing descriptions: {', '.join(missing_descriptions)}"
153 changes: 108 additions & 45 deletions packages/opal-common/opal_common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,24 @@
from opal_common.confi import Confi, confi

_LOG_FORMAT_WITHOUT_PID = "<green>{time}</green> | <blue>{name: <40}</blue>|<level>{level:^6} | {message}</level>\n{exception}"
_LOG_FORMAT_WITH_PID = "<green>{time}</green> | {process} | <blue>{name: <40}</blue>|<level>{level:^6} | {message}</level>\n{exception}"
_LOG_FORMAT_WITH_PID = "<green>{time}</green> | {process} | <blue>{name: <40></blue>|<level>{level:^6} | {message}</level>\n{exception}"

Choose a reason for hiding this comment

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

@onyedikachi-david why > and not }?



class OpalCommonConfig(Confi):
ALLOWED_ORIGINS = confi.list(
"ALLOWED_ORIGINS", ["*"], description="List of allowed origins for CORS"
"ALLOWED_ORIGINS",
["*"],
description="List of allowed origins for CORS. Use ['*'] to allow all origins, or specify a list of trusted domains for enhanced security in production environments."
)
# Process name to show in logs - Not confi-controlable on purpose
PROCESS_NAME = ""
# Logging
# - Log formatting
LOG_FORMAT_INCLUDE_PID = confi.bool("LOG_FORMAT_INCLUDE_PID", False)
LOG_FORMAT_INCLUDE_PID = confi.bool(
"LOG_FORMAT_INCLUDE_PID",
False,
description="Include process ID in log format. Useful for distinguishing between multiple OPAL processes in a distributed setup."
)
LOG_FORMAT = confi.str(
"LOG_FORMAT",
confi.delay(
Expand All @@ -26,99 +32,139 @@ class OpalCommonConfig(Confi):
else _LOG_FORMAT_WITHOUT_PID
)
),
description="The format of the log messages",
description="The format of the log messages. Automatically adjusts based on LOG_FORMAT_INCLUDE_PID setting.",
)
LOG_TRACEBACK = confi.bool(
"LOG_TRACEBACK", True, description="Include traceback in log messages"
"LOG_TRACEBACK",
True,
description="Include traceback in log messages. Helpful for debugging but may increase log verbosity."
)
LOG_DIAGNOSE = confi.bool(
"LOG_DIAGNOSE", True, description="Include diagnosis in log messages"
"LOG_DIAGNOSE",
True,
description="Include diagnosis in log messages. Provides additional context for troubleshooting."
)
LOG_COLORIZE = confi.bool(
"LOG_COLORIZE",
True,
description="Colorize log messages for improved readability in terminal output."
)
LOG_COLORIZE = confi.bool("LOG_COLORIZE", True, description="Colorize log messages")
LOG_SERIALIZE = confi.bool(
"LOG_SERIALIZE", False, description="Serialize log messages"
"LOG_SERIALIZE",
False,
description="Serialize log messages to JSON format. Useful for log aggregation and analysis tools."
)
LOG_SHOW_CODE_LINE = confi.bool(
"LOG_SHOW_CODE_LINE", True, description="Show code line in log messages"
"LOG_SHOW_CODE_LINE",
True,
description="Show code line in log messages. Aids in pinpointing the exact location of logged events in the source code."
)
# - log level
LOG_LEVEL = confi.str("LOG_LEVEL", "INFO", description="The log level to show")
LOG_LEVEL = confi.str(
"LOG_LEVEL",
"INFO",
description="The log level to show. Options: DEBUG, INFO, WARNING, ERROR, CRITICAL. Adjust based on desired verbosity and environment (e.g., DEBUG for development, INFO or WARNING for production)."
)
# - Which modules should be logged
LOG_MODULE_EXCLUDE_LIST = confi.list(
"LOG_MODULE_EXCLUDE_LIST",
[
"uvicorn",
# NOTE: the env var LOG_MODULE_EXCLUDE_OPA affects this list
],
description="List of modules to exclude from logging",
description="List of modules to exclude from logging. Use this to reduce noise from well-behaved third-party libraries."
)
LOG_MODULE_INCLUDE_LIST = confi.list(
"LOG_MODULE_INCLUDE_LIST",
["uvicorn.protocols.http"],
description="List of modules to include in logging",
description="List of modules to include in logging. Overrides LOG_MODULE_EXCLUDE_LIST for specific modules you want to monitor closely."
)
LOG_PATCH_UVICORN_LOGS = confi.bool(
"LOG_PATCH_UVICORN_LOGS",
True,
description="Should we takeover UVICORN's logs so they appear in the main logger",
description="Should we takeover UVICORN's logs so they appear in the main logger. Enables consistent logging format across OPAL and its web server."
)
# - Log to file as well ( @see https://github.com/Delgan/loguru#easier-file-logging-with-rotation--retention--compression)
LOG_TO_FILE = confi.bool(
"LOG_TO_FILE", False, description="Should we log to a file"
"LOG_TO_FILE",
False,
description="Enable logging to a file in addition to console output. Useful for persistent logs and post-mortem analysis."
)
LOG_FILE_PATH = confi.str(
"LOG_FILE_PATH",
f"opal_{PROCESS_NAME}{{time}}.log",
description="path to save log file",
description="Path to save log file. Supports time-based formatting for log rotation."
)
LOG_FILE_ROTATION = confi.str(
"LOG_FILE_ROTATION", "250 MB", description="Log file rotation size"
"LOG_FILE_ROTATION",
"250 MB",

Choose a reason for hiding this comment

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

@onyedikachi-david what is the format spec?

description="Log file rotation size. Helps manage disk space by creating new log files when the current one reaches this size."
)
LOG_FILE_RETENTION = confi.str(
"LOG_FILE_RETENTION", "10 days", description="Log file retention time"
"LOG_FILE_RETENTION",
"10 days",

Choose a reason for hiding this comment

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

@onyedikachi-david what is the format spec?

description="Log file retention time. Automatically removes old log files to prevent disk space issues."
)
LOG_FILE_COMPRESSION = confi.str(
"LOG_FILE_COMPRESSION", None, description="Log file compression format"
"LOG_FILE_COMPRESSION",
None,
description="Log file compression format. Set to 'gz' or 'zip' to compress rotated logs and save disk space."
)
LOG_FILE_SERIALIZE = confi.str(
"LOG_FILE_SERIALIZE", True, description="Serialize log messages in file"
"LOG_FILE_SERIALIZE",
True,
description="Serialize log messages in file to JSON format. Facilitates parsing and analysis by log management tools."
)
LOG_FILE_LEVEL = confi.str(
"LOG_FILE_LEVEL", "INFO", description="The log level to show in file"
"LOG_FILE_LEVEL",
"INFO",
description="The log level to show in file. Can be set differently from console logging for more detailed file logs."

Choose a reason for hiding this comment

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

@onyedikachi-david I would say it is usually for less details in persistent than the verbosity on screen. isn't it? so maybe for less or more detailed

)

STATISTICS_ENABLED = confi.bool(
"STATISTICS_ENABLED",
False,
description="Set if OPAL server will collect statistics about OPAL clients may cause a small performance hit",
description="Enable collection of statistics about OPAL clients. Useful for monitoring and optimization but may cause a small performance hit."
)
STATISTICS_ADD_CLIENT_CHANNEL = confi.str(
"STATISTICS_ADD_CLIENT_CHANNEL",
"__opal_stats_add",
description="The topic to update about new OPAL clients connection",
description="The topic to update about new OPAL clients connection. Used for real-time monitoring of client connections."
)
STATISTICS_REMOVE_CLIENT_CHANNEL = confi.str(
"STATISTICS_REMOVE_CLIENT_CHANNEL",
"__opal_stats_rm",
description="The topic to update about OPAL clients disconnection",
description="The topic to update about OPAL clients disconnection. Helps track client lifecycle and potential issues."
)

# Fetching Providers
# - where to load providers from
FETCH_PROVIDER_MODULES = confi.list(
"FETCH_PROVIDER_MODULES", ["opal_common.fetcher.providers"]
"FETCH_PROVIDER_MODULES",
["opal_common.fetcher.providers"],
description="Modules to load fetch providers from. Extend this list to add custom data fetching capabilities for policy and data updates."
)

# Fetching engine
# Max number of worker tasks handling fetch events concurrently
FETCHING_WORKER_COUNT = confi.int("FETCHING_WORKER_COUNT", 6)
# Time in seconds to wait on the queued fetch task.
FETCHING_CALLBACK_TIMEOUT = confi.int("FETCHING_CALLBACK_TIMEOUT", 10)
# Time in seconds to wait for queuing a new task (if the queue is full)
FETCHING_ENQUEUE_TIMEOUT = confi.int("FETCHING_ENQUEUE_TIMEOUT", 10)
FETCHING_WORKER_COUNT = confi.int(
"FETCHING_WORKER_COUNT",
6,
description="Number of worker tasks for handling fetch events concurrently. Adjust based on available resources and expected load."
)
FETCHING_CALLBACK_TIMEOUT = confi.int(
"FETCHING_CALLBACK_TIMEOUT",
10,
description="Timeout in seconds for fetch task callbacks. Prevents hanging on slow or unresponsive data sources."
)
FETCHING_ENQUEUE_TIMEOUT = confi.int(
"FETCHING_ENQUEUE_TIMEOUT",
10,
description="Timeout in seconds for enqueueing new fetch tasks. Helps manage backpressure in high-load scenarios."
)

GIT_SSH_KEY_FILE = confi.str(
"GIT_SSH_KEY_FILE", str(Path.home() / ".ssh/opal_repo_ssh_key")
"GIT_SSH_KEY_FILE",
str(Path.home() / ".ssh/opal_repo_ssh_key"),
description="Path to SSH key file for Git operations. Used for authenticating with private policy repositories."
)

# Trust self signed certificates (Advanced Usage - only affects OPAL client) -----------------------------
Expand All @@ -129,51 +175,68 @@ class OpalCommonConfig(Confi):
CLIENT_SELF_SIGNED_CERTIFICATES_ALLOWED = confi.bool(
"CLIENT_SELF_SIGNED_CERTIFICATES_ALLOWED",
False,
description="Whether or not OPAL Client will trust HTTPs connections protected by self signed certificates. DO NOT USE THIS IN PRODUCTION!",
description="Whether OPAL Client will trust HTTPS connections protected by self-signed certificates. CAUTION: Do not enable in production environments as it reduces security."
)
CLIENT_SSL_CONTEXT_TRUSTED_CA_FILE = confi.str(
"CLIENT_SSL_CONTEXT_TRUSTED_CA_FILE",
None,
description="A path to your own CA public certificate file (usually a .crt or a .pem file). Certificates signed by this issuer will be trusted by OPAL Client. DO NOT USE THIS IN PRODUCTION!",
description="Path to a custom CA public certificate file (.crt or .pem) for OPAL Client to trust. Use for organizational CAs in secure environments. NOT recommended for production use."
)

# security
AUTH_PUBLIC_KEY_FORMAT = confi.enum(
"AUTH_PUBLIC_KEY_FORMAT", EncryptionKeyFormat, EncryptionKeyFormat.ssh
"AUTH_PUBLIC_KEY_FORMAT",
EncryptionKeyFormat,
EncryptionKeyFormat.ssh,
description="Format of the public key used for authentication. Supports SSH and PEM formats for flexibility in key management."
)
AUTH_PUBLIC_KEY = confi.delay(
lambda AUTH_PUBLIC_KEY_FORMAT=None: confi.public_key(
"AUTH_PUBLIC_KEY", default=None, key_format=AUTH_PUBLIC_KEY_FORMAT
"AUTH_PUBLIC_KEY",
default=None,
key_format=AUTH_PUBLIC_KEY_FORMAT,
description="The public key used for authentication and JWT token verification. Critical for securing communication between OPAL components."
)
)
AUTH_JWT_ALGORITHM = confi.enum(
"AUTH_JWT_ALGORITHM",
JWTAlgorithm,
getattr(JWTAlgorithm, "RS256"),
description="jwt algorithm, possible values: see: https://pyjwt.readthedocs.io/en/stable/algorithms.html",
description="JWT algorithm for token signing and verification. RS256 is recommended for production use. See: https://pyjwt.readthedocs.io/en/stable/algorithms.html"
)
AUTH_JWT_AUDIENCE = confi.str(
"AUTH_JWT_AUDIENCE",
"https://api.opal.ac/v1/",
description="Audience claim for JWT tokens. Should match the intended recipient of the token, typically the OPAL API endpoint."
)
AUTH_JWT_ISSUER = confi.str(
"AUTH_JWT_ISSUER",
f"https://opal.ac/",
description="Issuer claim for JWT tokens. Identifies the token issuer, usually set to your OPAL server's domain."
)
AUTH_JWT_AUDIENCE = confi.str("AUTH_JWT_AUDIENCE", "https://api.opal.ac/v1/")
AUTH_JWT_ISSUER = confi.str("AUTH_JWT_ISSUER", f"https://opal.ac/")
POLICY_REPO_POLICY_EXTENSIONS = confi.list(
"POLICY_REPO_POLICY_EXTENSIONS",
[".rego"],
description="List of extensions to serve as policy modules",
description="List of file extensions to recognize as policy modules. Extend this list if using custom policy file types beyond Rego."
)

ENABLE_METRICS = confi.bool("ENABLE_METRICS", False)
ENABLE_METRICS = confi.bool(
"ENABLE_METRICS",
False,
description="Enable metrics collection for monitoring OPAL performance and behavior. Useful for operational insights and troubleshooting."
)

# optional APM tracing with datadog
ENABLE_DATADOG_APM = confi.bool(
"ENABLE_DATADOG_APM",
False,
description="Set if OPAL server should enable tracing with datadog APM",
description="Enable tracing with Datadog APM for advanced performance monitoring and distributed tracing capabilities."
)
HTTP_FETCHER_PROVIDER_CLIENT = confi.str(
"HTTP_FETCHER_PROVIDER_CLIENT",
"aiohttp",
description="The client to use for fetching data, can be either aiohttp or httpx."
"if provided different value, aiohttp will be used.",
description="The HTTP client library to use for fetching data. Options: 'aiohttp' or 'httpx'. Affects performance and features of HTTP-based data fetching."
)


opal_common_config = OpalCommonConfig(prefix="OPAL_")
opal_common_config = OpalCommonConfig(prefix="OPAL_")
13 changes: 13 additions & 0 deletions packages/opal-common/opal_common/tests/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import pytest
from opal_common.config import opal_common_config
from opal_common.confi.types import ConfiEntry


def test_common_config_descriptions():

Choose a reason for hiding this comment

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

@onyedikachi-david why duplicate the test code and not use a common shared config_test.py class?

missing_descriptions = []

for key, entry in opal_common_config.entries.items():
if isinstance(entry, ConfiEntry) and not entry.description:
missing_descriptions.append(key)

assert not missing_descriptions, f"The following config variables are missing descriptions: {', '.join(missing_descriptions)}"
Loading