"""APIs to create Split.io client connections.""" import logging from typing import Any, Dict, Optional from secrets_manager.python_ext import PythonSecretsManager from splitio import get_factory from splitio.client.factory import Client as SplitioClient from splitio.exceptions import TimeoutException from pdp import config FEATURE_DISABLED = "control" FEATURE_ON = "on" FEATURE_OFF = "off" IDENTITY_ID_ATTRIBUTE = "identity_id" # Tenant-based context (not used yet) COMPANY_BRAND_UUID = "company_brand_uuid" VENDOR_UUID = "vendor_uuid" SUBACCOUNT_UUID = "subaccount_uuid" logger = logging.getLogger(__name__) SPLIT_BLOCK_UNTIL_READY_TIMEOUT = 60 APPLICATION = "split" API_KEY_SECRET = "API_KEY" SPLITIO_CONFIG_API_KEY = "apiKey" ALLOWED_LOCAL_DEV_ENVIRONMENTS = (config.DEV_ENVIRONMENT, config.TEST_ENVIRONMENT) def splitio_client_factory() -> SplitioClient: """Generate splitio client objects.""" splitio_config = build_split_io_config() assert splitio_config.get(SPLITIO_CONFIG_API_KEY), "Could not find SPLITIO API_KEY." factory = get_factory( api_key=splitio_config[SPLITIO_CONFIG_API_KEY], config=splitio_config ) try: factory.block_until_ready(SPLIT_BLOCK_UNTIL_READY_TIMEOUT) except TimeoutException as e: logger.error("Connection timeout to splitio") raise e return factory.client() def build_split_io_config() -> Dict[str, Any]: """Generate the splitio client configuration. Attempts to fetch the API key in this order: 1. Use key from the environment. 2. Fetch key from SecretManager. 3. Fallback to localhost mode if env is 'dev' or 'test'. """ splitio_config = { # impressionsMode: Defines how impressions are queued on the SDK. # Supported modes are OPTIMIZED, NONE, and DEBUG "impressionsMode": config.SPLITIO_IMPRESSIONS_MODE, # preforkedInitialization # Flag for enabling fork execution in preforked-type servers # such as uWSGI or GUnicorn. Disabled because the PDP docker # container uses uvicorn (see uvicorn-start.sh) "preforkedInitialization": config.SPLITIO_PREFORKED_INIT, } if config.SPLITIO_API_KEY: # Found API_KEY in ENV splitio_config.update({SPLITIO_CONFIG_API_KEY: config.SPLITIO_API_KEY}) else: # Try fetching from secret manager. splitio_config.update( {SPLITIO_CONFIG_API_KEY: fetch_api_key_from_secrets_manager()} ) if config.ENVIRONMENT in ALLOWED_LOCAL_DEV_ENVIRONMENTS: # Localhost configs. if not splitio_config.get(SPLITIO_CONFIG_API_KEY): # Running in dev or test. Fallback to "localhost" key splitio_config.update( {SPLITIO_CONFIG_API_KEY: config.SPLITIO_LOCALHOST_ENV} ) splitio_config.update( { "splitFile": config.SPLIT_FILE_PATH, } ) return splitio_config def fetch_api_key_from_secrets_manager() -> Optional[str]: """Fetch the splitio api key from SecretsManager.""" api_key: Optional[str] = None try: secrets_manager_client = PythonSecretsManager( environment=config.ENVIRONMENT, service_name=APPLICATION ) # `get_cred` returns 'None' if the key is not found api_key = secrets_manager_client.get_cred(API_KEY_SECRET) if api_key: logger.info("SecretsManager client found split.io API_KEY secret.") except Exception as e: logger.warning( f"SecretsManager client cannot fetch split.io API_KEY secret:" f" {config.ENVIRONMENT}/{APPLICATION}/{API_KEY_SECRET}. Error: {str(e)}" ) return api_key