"""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 contributor import config FEATURE_DISABLED = "control" FEATURE_ON = "on" FEATURE_OFF = "off" IDENTITY_ID_ATTRIBUTE = "identity_id" 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 error: logger.error("Connection timeout to splitio") raise error return factory.client() def build_split_io_config() -> Dict[str, Any]: """Generate 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 controls fork-safe init in preforked servers. # Disabled by default because contributor runs via uvicorn. "preforkedInitialization": config.SPLITIO_PREFORKED_INIT, } if config.SPLITIO_API_KEY: splitio_config.update({SPLITIO_CONFIG_API_KEY: config.SPLITIO_API_KEY}) else: splitio_config.update( {SPLITIO_CONFIG_API_KEY: fetch_api_key_from_secrets_manager()} ) if config.ENVIRONMENT in ALLOWED_LOCAL_DEV_ENVIRONMENTS: if not splitio_config.get(SPLITIO_CONFIG_API_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 error: logger.warning( "SecretsManager client cannot fetch split.io API_KEY secret: " "%s/%s/%s. Error: %s", config.ENVIRONMENT, APPLICATION, API_KEY_SECRET, str(error), ) return api_key