Skip to content
Merged
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
38 changes: 19 additions & 19 deletions src/splunk_ao/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class ConfigKey:
Attributes
----------
name : str
The attribute name used in the Configuration class (e.g., "galileo_api_key").
The attribute name used in the Configuration class (e.g., "splunk_ao_api_key").
env_var : str
The corresponding environment variable name (e.g., "SPLUNK_AO_API_KEY").
description : str
Expand Down Expand Up @@ -71,7 +71,7 @@ def parse_log_level(value: str) -> str:

_CONFIGURATION_KEYS = [
ConfigKey(
name="galileo_api_key",
name="splunk_ao_api_key",
env_var="SPLUNK_AO_API_KEY",
description="API key for authenticating with Splunk Agent Observability",
required=True,
Expand Down Expand Up @@ -166,17 +166,17 @@ class ConfigurationMeta(type):

This metaclass enables the Configuration class to provide dynamic attribute access
with automatic resolution from multiple sources. When accessing a configuration
attribute (e.g., `Configuration.galileo_api_key`), the metaclass:
attribute (e.g., `Configuration.splunk_ao_api_key`), the metaclass:

1. Checks if the attribute is in _CONFIGURATION_KEYS
2. Resolves the value from: explicit value → environment variable → .env file → default
3. Applies type conversion and validation if a parser is defined
4. Returns the resolved value

When setting a configuration attribute (e.g., `Configuration.galileo_api_key = "key"`),
When setting a configuration attribute (e.g., `Configuration.splunk_ao_api_key = "key"`),
the metaclass:

1. Stores the value internally (with underscore prefix: `_galileo_api_key`)
1. Stores the value internally (with underscore prefix: `_splunk_ao_api_key`)
2. Automatically updates the corresponding environment variable
3. Ensures compatibility with libraries that read from os.environ

Expand Down Expand Up @@ -241,7 +241,7 @@ def __setattr__(cls, name: str, value: Any) -> None:
Set configuration attribute and sync to environment variable.

When setting a configuration key, the value is:
1. Stored internally with underscore prefix (e.g., `_galileo_api_key`)
1. Stored internally with underscore prefix (e.g., `_splunk_ao_api_key`)
2. Synced to the corresponding environment variable (e.g., `SPLUNK_AO_API_KEY`)

This ensures that both the Configuration class and environment variables
Expand Down Expand Up @@ -288,29 +288,29 @@ class Configuration(metaclass=ConfigurationMeta):
Attributes
----------
Configuration attributes are dynamically provided based on _CONFIGURATION_KEYS:
galileo_api_key (str): API key for Galileo authentication (sensitive)
console_url (str): URL of the Galileo console
splunk_ao_api_key (str): API key for Splunk AO authentication (sensitive)
console_url (str): URL of the Splunk AO console
openai_api_key (str): OpenAI API key for SDK interoperability (sensitive)
default_project_name (str): Default project name
default_project_id (str): Default project ID
default_logstream_name (str): Default log stream name
default_logstream_id (str): Default log stream ID
logging_disabled (bool): Disable all telemetry logging to Galileo
logging_disabled (bool): Disable all telemetry logging to Splunk AO
log_level (str): Python logging level for SDK console output

Examples
--------
Reading configuration values:
```python
# Access via class attribute (reads from env vars, .env, or defaults)
api_key = Configuration.galileo_api_key
api_key = Configuration.splunk_ao_api_key
url = Configuration.console_url
```

Setting configuration values:
```python
# Set explicitly (also updates environment variables)
Configuration.galileo_api_key = "your-api-key"
Configuration.splunk_ao_api_key = "your-api-key"
Configuration.console_url = "your-console-url"
```

Expand All @@ -325,7 +325,7 @@ class Configuration(metaclass=ConfigurationMeta):
```python
# Get all configuration values (sensitive values are masked)
config = Configuration.get_configuration()
print(config["galileo_api_key"]) # Output: "***"
print(config["splunk_ao_api_key"]) # Output: "***"
print(config["console_url"]) # Output: actual URL
```

Expand All @@ -348,7 +348,7 @@ class Configuration(metaclass=ConfigurationMeta):
Notes
-----
- The Configuration class should not be instantiated; use it as a static class
- Direct attribute access (e.g., `Configuration.galileo_api_key`) is the recommended pattern
- Direct attribute access (e.g., `Configuration.splunk_ao_api_key`) is the recommended pattern
- The `get_configuration()` method masks sensitive values for safe display/logging
- Setting an attribute automatically updates the corresponding environment variable
- This design maintains compatibility with third-party libraries expecting env vars
Expand Down Expand Up @@ -383,22 +383,22 @@ def _load_env_file(cls) -> None:

@classmethod
def connect(cls) -> None:
"""Validate configuration and connect to Galileo."""
"""Validate configuration and connect to Splunk AO."""
cls._load_env_file()

if not cls.galileo_api_key:
if not cls.splunk_ao_api_key:
raise ConfigurationError(
"Splunk AO API key is required. Set Configuration.galileo_api_key or SPLUNK_AO_API_KEY."
"Splunk AO API key is required. Set Configuration.splunk_ao_api_key or SPLUNK_AO_API_KEY."
)

logger.info("Validating Galileo configuration and connectivity...")
logger.info("Validating Splunk AO configuration and connectivity...")

try:
SplunkAOConfig.get()
logger.info("Successfully connected to Galileo")
logger.info("Successfully connected to Splunk AO")
except Exception as e:
error_msg = str(e)
logger.error(f"Failed to connect to Galileo: {error_msg}")
logger.error(f"Failed to connect to Splunk AO: {error_msg}")

error_lower = error_msg.lower()
if ("api" in error_lower and "key" in error_lower) or "auth" in error_lower:
Expand Down
46 changes: 23 additions & 23 deletions tests/test_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,8 @@ def test_property_precedence_internal_over_env(
"""Test that internal state takes precedence over environment variables for all keys."""
# Test with a few representative keys
test_cases = [
("galileo_api_key", "env-key", "internal-key"),
("console_url", "https://env.galileo.ai", "https://internal.galileo.ai"),
("splunk_ao_api_key", "env-key", "internal-key"),
("console_url", "https://env.splunkao.ai", "https://internal.splunkao.ai"),
("default_project_name", "env-project", "internal-project"),
]

Expand All @@ -172,16 +172,16 @@ def test_env_file_loading_sets_environment_variables(
# Create .env file with test values
mock_env_file.write_text(
'SPLUNK_AO_API_KEY="env-file-key"\n'
'SPLUNK_AO_CONSOLE_URL="https://envfile.galileo.ai"\n'
'SPLUNK_AO_CONSOLE_URL="https://envfile.splunkao.ai"\n'
'OPENAI_API_KEY="env-file-openai"\n'
)

# Access property to trigger env file loading
api_key = Configuration.galileo_api_key
api_key = Configuration.splunk_ao_api_key

# Verify env file was loaded
assert api_key == "env-file-key"
assert Configuration.console_url == "https://envfile.galileo.ai"
assert Configuration.console_url == "https://envfile.splunkao.ai"
assert Configuration.openai_api_key == "env-file-openai"

def test_env_file_does_not_override_existing_env_vars(
Expand All @@ -195,7 +195,7 @@ def test_env_file_does_not_override_existing_env_vars(
mock_env_file.write_text('SPLUNK_AO_API_KEY="env-file-key"\n')

# Access property to trigger env file loading
api_key = Configuration.galileo_api_key
api_key = Configuration.splunk_ao_api_key

# Verify existing env var is preserved
assert api_key == "existing-env-key"
Expand All @@ -215,10 +215,10 @@ def test_env_file_handles_comments_and_empty_lines(
)

# Trigger loading
_ = Configuration.galileo_api_key
_ = Configuration.splunk_ao_api_key

# Verify all values are loaded correctly
assert Configuration.galileo_api_key == "key-without-quotes"
assert Configuration.splunk_ao_api_key == "key-without-quotes"
assert Configuration.console_url == "url-with-double-quotes"
assert Configuration.openai_api_key == "key-with-single-quotes"

Expand All @@ -229,7 +229,7 @@ def test_env_file_loading_is_idempotent(
mock_env_file.write_text('SPLUNK_AO_API_KEY="test-key"\n')

# Access multiple times
_ = Configuration.galileo_api_key
_ = Configuration.splunk_ao_api_key
_ = Configuration.console_url
_ = Configuration.openai_api_key

Expand All @@ -250,7 +250,7 @@ def test_malformed_env_file_does_not_crash(
mock_env_file.write_text("INVALID_LINE_WITHOUT_EQUALS\n")

# This should not raise an exception
api_key = Configuration.galileo_api_key
api_key = Configuration.splunk_ao_api_key

# Verify operation continues (returns None since no valid config)
assert api_key is None
Expand All @@ -271,15 +271,15 @@ def test_connect_succeeds_with_valid_configuration(

# Set valid configuration
monkeypatch.setenv("SPLUNK_AO_API_KEY", "valid-key")
monkeypatch.setenv("SPLUNK_AO_CONSOLE_URL", "https://app.galileo.ai")
monkeypatch.setenv("SPLUNK_AO_CONSOLE_URL", "https://app.splunkao.ai")

# Connect should succeed without raising
Configuration.connect()

# Verify logging occurred (no print statements)
logs = log_stream.getvalue()
assert "Validating Galileo configuration" in logs
assert "Successfully connected to Galileo" in logs
assert "Validating Splunk AO configuration" in logs
assert "Successfully connected to Splunk AO" in logs

def test_connect_passes_without_console_url(
self,
Expand All @@ -299,8 +299,8 @@ def test_connect_passes_without_console_url(

# Verify logging occurred (no print statements)
logs = log_stream.getvalue()
assert "Validating Galileo configuration" in logs
assert "Successfully connected to Galileo" in logs
assert "Validating Splunk AO configuration" in logs
assert "Successfully connected to Splunk AO" in logs

# Verify console URL is set to default
assert Configuration.console_url == "https://app.galileo.ai/"
Expand All @@ -316,15 +316,15 @@ def test_connect_fails_without_api_key(
_, log_stream = capture_logs

# Set only console URL
monkeypatch.setenv("SPLUNK_AO_CONSOLE_URL", "https://app.galileo.ai")
monkeypatch.setenv("SPLUNK_AO_CONSOLE_URL", "https://app.splunkao.ai")

# Should raise ConfigurationError
with pytest.raises(ConfigurationError) as exc_info:
Configuration.connect()

# Verify error message provides helpful guidance
assert "splunk ao api key is required" in str(exc_info.value).lower()
assert "Configuration.galileo_api_key" in str(exc_info.value)
assert "Configuration.splunk_ao_api_key" in str(exc_info.value)

@pytest.mark.parametrize(
"error_type,error_message,expected_in_error",
Expand All @@ -351,7 +351,7 @@ def test_connect_handles_different_error_types(

# Set valid configuration
monkeypatch.setenv("SPLUNK_AO_API_KEY", "valid-key")
monkeypatch.setenv("SPLUNK_AO_CONSOLE_URL", "https://app.galileo.ai")
monkeypatch.setenv("SPLUNK_AO_CONSOLE_URL", "https://app.splunkao.ai")

# Mock SplunkAOConfig.get to raise appropriate error
mock_config_get.side_effect = Exception(error_message)
Expand All @@ -364,7 +364,7 @@ def test_connect_handles_different_error_types(

# Verify error is logged
logs = log_stream.getvalue()
assert "Failed to connect to Galileo" in logs
assert "Failed to connect to Splunk AO" in logs


class TestConfigurationReset:
Expand Down Expand Up @@ -454,8 +454,8 @@ class TestConfigurationGetConfiguration:
def test_get_configuration_returns_all_keys(self, clean_env: None, reset_configuration: None) -> None:
"""Test get_configuration() returns all keys from CONFIGURATION_KEYS."""
# Set a few values
Configuration.galileo_api_key = "test-key"
Configuration.console_url = "https://test.galileo.ai"
Configuration.splunk_ao_api_key = "test-key"
Configuration.console_url = "https://test.splunkao.ai"

config = Configuration.get_configuration()

Expand Down Expand Up @@ -532,8 +532,8 @@ def test_complete_configuration_workflow(

# Verify logging was used throughout
logs = log_stream.getvalue()
assert "Validating Galileo configuration" in logs
assert "Successfully connected to Galileo" in logs
assert "Validating Splunk AO configuration" in logs
assert "Successfully connected to Splunk AO" in logs

def test_configuration_with_env_file_workflow(
self, mock_env_file: Path, clean_env: None, reset_configuration: None, mock_api_endpoints: MagicMock
Expand Down
Loading