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
6 changes: 3 additions & 3 deletions src/launch_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
logger = logging_config.logging.getLogger("launch_client")

# Import launch_server if it exists
LAUNCH_SERVER_EXISTS = True
launch_server_exists = True
try:
from launch_server import start_server, get_api_key

Expand All @@ -31,7 +31,7 @@
except ImportError as ex:
logger.debug("API Server not present: %s", ex)
os.environ.pop("API_SERVER_CONTROL", None)
LAUNCH_SERVER_EXISTS = False
launch_server_exists = False

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

Expand Down Expand Up @@ -159,7 +159,7 @@ def main() -> None:
if __name__ == "__main__":
# Start Server if not running
init_server_state()
if LAUNCH_SERVER_EXISTS:
if launch_server_exists:
try:
logger.debug("Server PID: %i", state.server["pid"])
except KeyError:
Expand Down
16 changes: 0 additions & 16 deletions src/server/api/core/bootstrap.py

This file was deleted.

2 changes: 1 addition & 1 deletion src/server/api/core/databases.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""

from typing import Optional, Union
from server.api.core import bootstrap
from server.bootstrap import bootstrap

from common.schema import Database, DatabaseNameType
from common import logging_config
Expand Down
2 changes: 1 addition & 1 deletion src/server/api/core/prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# spell-checker:ignore

from typing import Optional, Union
from server.api.core import bootstrap
from server.bootstrap import bootstrap

from common.schema import PromptCategoryType, PromptNameType, Prompt
from common import logging_config
Expand Down
2 changes: 1 addition & 1 deletion src/server/api/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import os
import copy
import json
from server.api.core import bootstrap
from server.bootstrap import bootstrap

from common.schema import Settings, Configuration, ClientIdType
from common import logging_config
Expand Down
2 changes: 1 addition & 1 deletion src/server/api/utils/databases.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def connect(config: Database) -> oracledb.Connection:
raise
except OSError as ex:
raise ConnectionError(f"Error connecting to database: {ex}") from ex

logger.debug("Connected to Databases: %s", config.dsn)

return conn
Expand Down
16 changes: 12 additions & 4 deletions src/server/api/utils/oci.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import oci

from server.bootstrap.bootstrap import OCI_OBJECTS, SETTINGS_OBJECTS
from server.bootstrap import bootstrap
from common.schema import OracleCloudSettings, ClientIdType, OCIProfileType
from common import logging_config

Expand Down Expand Up @@ -48,12 +48,19 @@ def get(
if client is not None and auth_profile is not None:
raise ValueError("provide either 'client' or 'auth_profile', not both")

oci_objects = OCI_OBJECTS
oci_objects = bootstrap.OCI_OBJECTS
if client is not None:
# Get client settings directly from SETTINGS_OBJECTS
client_settings = next((s for s in SETTINGS_OBJECTS if s.client == client), None)
logger.debug("Looking for client %s in SETTINGS_OBJECTS", client)
logger.debug(
"SETTINGS_OBJECTS has %d entries: %s",
len(bootstrap.SETTINGS_OBJECTS),
[s.client for s in bootstrap.SETTINGS_OBJECTS],
)
client_settings = next((s for s in bootstrap.SETTINGS_OBJECTS if s.client == client), None)
if not client_settings:
raise ValueError(f"client {client} not found")
available_clients = [s.client for s in bootstrap.SETTINGS_OBJECTS]
raise ValueError(f"client {client} not found in SETTINGS_OBJECTS with clients: {available_clients}")

derived_auth_profile = (
getattr(client_settings.oci, "auth_profile", "DEFAULT") if client_settings.oci else "DEFAULT"
Expand Down Expand Up @@ -275,6 +282,7 @@ def get_genai_models(config: OracleCloudSettings, regional: bool = False) -> lis
"id": model.id,
}
)
logger.info("Registered %i GenAI Models", len(genai_models))
except oci.exceptions.ServiceError as ex:
logger.info("Unable to get GenAI Models in Region: %s (%s)", region["region_name"], ex.message)
except (oci.exceptions.RequestException, urllib3.exceptions.MaxRetryError):
Expand Down
4 changes: 3 additions & 1 deletion src/server/bootstrap/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
"""
# spell-checker:ignore genai

from server.bootstrap import models, oci, settings
from server.bootstrap import databases, models, oci, prompts, settings
from common import logging_config

logger = logging_config.logging.getLogger("bootstrap")

DATABASE_OBJECTS = databases.main()
MODEL_OBJECTS = models.main()
OCI_OBJECTS = oci.main()
PROMPT_OBJECTS = prompts.main()
SETTINGS_OBJECTS = settings.main()
83 changes: 44 additions & 39 deletions src/server/bootstrap/oci.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,49 @@
logger = logging_config.logging.getLogger("bootstrap.oci")


def _apply_env_overrides_to_default_profile(config: list[dict]) -> None:
"""Apply environment variable overrides to the DEFAULT OCI profile."""
def override(profile: dict, key: str, env_key: str, env: dict, overrides: dict, default=None):
val = env.get(env_key)
if val is not None and val != profile.get(key):
overrides[key] = (profile.get(key), val)
return val
return profile.get(key, default)

env = os.environ

for profile in config:
if profile["auth_profile"] == oci.config.DEFAULT_PROFILE:
overrides = {}

profile.update(
{
"tenancy": override(profile, "tenancy", "OCI_CLI_TENANCY", env, overrides),
"region": override(profile, "region", "OCI_CLI_REGION", env, overrides),
"user": override(profile, "user", "OCI_CLI_USER", env, overrides),
"fingerprint": override(profile, "fingerprint", "OCI_CLI_FINGERPRINT", env, overrides),
"key_file": override(profile, "key_file", "OCI_CLI_KEY_FILE", env, overrides),
"security_token_file": override(
profile, "security_token_file", "OCI_CLI_SECURITY_TOKEN_FILE", env, overrides
),
"authentication": env.get("OCI_CLI_AUTH")
or ("security_token" if profile.get("security_token_file") else "api_key"),
"genai_compartment_id": override(
profile, "genai_compartment_id", "OCI_GENAI_COMPARTMENT_ID", env, overrides, None
),
"genai_region": override(profile, "genai_region", "OCI_GENAI_REGION", env, overrides, None),
"log_requests": profile.get("log_requests", False),
"additional_user_agent": profile.get("additional_user_agent", ""),
"pass_phrase": profile.get("pass_phrase"),
}
)

if overrides:
logger.info("Environment variable overrides for OCI DEFAULT profile:")
for key, (old, new) in overrides.items():
logger.info(" %s: '%s' -> '%s'", key, old, new)


def main() -> list[OracleCloudSettings]:
"""Read in OCI Configuration options into an object"""
logger.debug("*** Bootstrapping OCI - Start")
Expand Down Expand Up @@ -62,45 +105,7 @@ def main() -> list[OracleCloudSettings]:
config.append({"auth_profile": oci.config.DEFAULT_PROFILE})

# Override DEFAULT profile with environment variables
def override(profile: dict, key: str, env_key: str, env: dict, overrides: dict, default=None):
val = env.get(env_key)
if val is not None and val != profile.get(key):
overrides[key] = (profile.get(key), val)
return val
return profile.get(key, default)

env = os.environ

for profile in config:
if profile["auth_profile"] == oci.config.DEFAULT_PROFILE:
overrides = {}

profile.update(
{
"tenancy": override(profile, "tenancy", "OCI_CLI_TENANCY", env, overrides),
"region": override(profile, "region", "OCI_CLI_REGION", env, overrides),
"user": override(profile, "user", "OCI_CLI_USER", env, overrides),
"fingerprint": override(profile, "fingerprint", "OCI_CLI_FINGERPRINT", env, overrides),
"key_file": override(profile, "key_file", "OCI_CLI_KEY_FILE", env, overrides),
"security_token_file": override(
profile, "security_token_file", "OCI_CLI_SECURITY_TOKEN_FILE", env, overrides
),
"authentication": env.get("OCI_CLI_AUTH")
or ("security_token" if profile.get("security_token_file") else "api_key"),
"genai_compartment_id": override(
profile, "genai_compartment_id", "OCI_GENAI_COMPARTMENT_ID", env, overrides, None
),
"genai_region": override(profile, "genai_region", "OCI_GENAI_REGION", env, overrides, None),
"log_requests": profile.get("log_requests", False),
"additional_user_agent": profile.get("additional_user_agent", ""),
"pass_phrase": profile.get("pass_phrase"),
}
)

if overrides:
logger.info("Environment variable overrides for OCI DEFAULT profile:")
for key, (old, new) in overrides.items():
logger.info(" %s: '%s' -> '%s'", key, old, new)
_apply_env_overrides_to_default_profile(config)

# Build final OracleCloudSettings objects
oci_objects = []
Expand Down
2 changes: 1 addition & 1 deletion tests/server/unit/api/core/test_core_databases.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import pytest

from server.api.core import databases
from server.api.core import bootstrap
from server.bootstrap import bootstrap
from common.schema import Database


Expand Down
2 changes: 1 addition & 1 deletion tests/server/unit/api/utils/test_utils_databases.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from server.api.utils import databases
from server.api.utils.databases import DbException
from server.api.core import bootstrap
from server.bootstrap import bootstrap
from common.schema import Database


Expand Down
87 changes: 59 additions & 28 deletions tests/server/unit/api/utils/test_utils_oci.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ def setup_method(self):
)
self.sample_client_settings = Settings(client="test_client", oci=OciSettings(auth_profile="CUSTOM"))

@patch("server.api.utils.oci.OCI_OBJECTS", [])
@patch("server.bootstrap.bootstrap.OCI_OBJECTS", [])
def test_get_no_objects_configured(self):
"""Test getting OCI settings when none are configured"""
with pytest.raises(ValueError, match="not configured"):
oci_utils.get()

@patch("server.api.utils.oci.OCI_OBJECTS", new_callable=list)
@patch("server.bootstrap.bootstrap.OCI_OBJECTS", new_callable=list)
def test_get_all(self, mock_oci_objects):
"""Test getting all OCI settings when no filters are provided"""
all_oci = [self.sample_oci_default, self.sample_oci_custom]
Expand All @@ -54,7 +54,7 @@ def test_get_all(self, mock_oci_objects):

assert result == all_oci

@patch("server.api.utils.oci.OCI_OBJECTS")
@patch("server.bootstrap.bootstrap.OCI_OBJECTS")
def test_get_by_auth_profile_found(self, mock_oci_objects):
"""Test getting OCI settings by auth_profile when it exists"""
mock_oci_objects.__iter__ = MagicMock(return_value=iter([self.sample_oci_default, self.sample_oci_custom]))
Expand All @@ -63,55 +63,86 @@ def test_get_by_auth_profile_found(self, mock_oci_objects):

assert result == self.sample_oci_custom

@patch("server.api.utils.oci.OCI_OBJECTS")
@patch("server.bootstrap.bootstrap.OCI_OBJECTS")
def test_get_by_auth_profile_not_found(self, mock_oci_objects):
"""Test getting OCI settings by auth_profile when it doesn't exist"""
mock_oci_objects.__iter__ = MagicMock(return_value=iter([self.sample_oci_default]))

with pytest.raises(ValueError, match="profile 'NONEXISTENT' not found"):
oci_utils.get(auth_profile="NONEXISTENT")

@patch("server.api.utils.oci.OCI_OBJECTS")
@patch("server.api.utils.oci.SETTINGS_OBJECTS")
def test_get_by_client_with_oci_settings(self, mock_settings_objects, mock_oci_objects):
def test_get_by_client_with_oci_settings(self):
"""Test getting OCI settings by client when client has OCI settings"""
mock_settings_objects.__iter__ = MagicMock(return_value=iter([self.sample_client_settings]))
mock_oci_objects.__iter__ = MagicMock(return_value=iter([self.sample_oci_default, self.sample_oci_custom]))
from server.bootstrap import bootstrap

result = oci_utils.get(client="test_client")
# Save originals
orig_settings = bootstrap.SETTINGS_OBJECTS
orig_oci = bootstrap.OCI_OBJECTS

assert result == self.sample_oci_custom
try:
# Replace with test data
bootstrap.SETTINGS_OBJECTS = [self.sample_client_settings]
bootstrap.OCI_OBJECTS = [self.sample_oci_default, self.sample_oci_custom]

result = oci_utils.get(client="test_client")

@patch("server.api.utils.oci.OCI_OBJECTS")
@patch("server.api.utils.oci.SETTINGS_OBJECTS")
def test_get_by_client_without_oci_settings(self, mock_settings_objects, mock_oci_objects):
assert result == self.sample_oci_custom
finally:
# Restore originals
bootstrap.SETTINGS_OBJECTS = orig_settings
bootstrap.OCI_OBJECTS = orig_oci

def test_get_by_client_without_oci_settings(self):
"""Test getting OCI settings by client when client has no OCI settings"""
from server.bootstrap import bootstrap

client_settings_no_oci = Settings(client="test_client", oci=None)
mock_settings_objects.__iter__ = MagicMock(return_value=iter([client_settings_no_oci]))
mock_oci_objects.__iter__ = MagicMock(return_value=iter([self.sample_oci_default]))

result = oci_utils.get(client="test_client")
# Save originals
orig_settings = bootstrap.SETTINGS_OBJECTS
orig_oci = bootstrap.OCI_OBJECTS

try:
# Replace with test data
bootstrap.SETTINGS_OBJECTS = [client_settings_no_oci]
bootstrap.OCI_OBJECTS = [self.sample_oci_default]

result = oci_utils.get(client="test_client")

assert result == self.sample_oci_default
assert result == self.sample_oci_default
finally:
# Restore originals
bootstrap.SETTINGS_OBJECTS = orig_settings
bootstrap.OCI_OBJECTS = orig_oci

@patch("server.api.utils.oci.OCI_OBJECTS")
@patch("server.api.utils.oci.SETTINGS_OBJECTS")
@patch("server.bootstrap.bootstrap.OCI_OBJECTS")
@patch("server.bootstrap.bootstrap.SETTINGS_OBJECTS")
def test_get_by_client_not_found(self, mock_settings_objects, mock_oci_objects):
"""Test getting OCI settings when client doesn't exist"""
mock_settings_objects.__iter__ = MagicMock(return_value=iter([]))

with pytest.raises(ValueError, match="client test_client not found"):
oci_utils.get(client="test_client")

@patch("server.api.utils.oci.OCI_OBJECTS")
@patch("server.api.utils.oci.SETTINGS_OBJECTS")
def test_get_by_client_no_matching_profile(self, mock_settings_objects, mock_oci_objects):
def test_get_by_client_no_matching_profile(self):
"""Test getting OCI settings by client when no matching profile exists"""
mock_settings_objects.__iter__ = MagicMock(return_value=iter([self.sample_client_settings]))
mock_oci_objects.__iter__ = MagicMock(return_value=iter([self.sample_oci_default])) # Only DEFAULT profile

with pytest.raises(ValueError, match="No settings found for client 'test_client' with auth_profile 'CUSTOM'"):
oci_utils.get(client="test_client")
from server.bootstrap import bootstrap

# Save originals
orig_settings = bootstrap.SETTINGS_OBJECTS
orig_oci = bootstrap.OCI_OBJECTS

try:
# Replace with test data
bootstrap.SETTINGS_OBJECTS = [self.sample_client_settings]
bootstrap.OCI_OBJECTS = [self.sample_oci_default] # Only DEFAULT profile

with pytest.raises(ValueError, match="No settings found for client 'test_client' with auth_profile 'CUSTOM'"):
oci_utils.get(client="test_client")
finally:
# Restore originals
bootstrap.SETTINGS_OBJECTS = orig_settings
bootstrap.OCI_OBJECTS = orig_oci

def test_get_both_client_and_auth_profile(self):
"""Test that providing both client and auth_profile raises an error"""
Expand Down
Loading
Loading