"""Apply-mode config for data-access provider mutation.""" from __future__ import annotations import re from collections.abc import Mapping from dataclasses import dataclass from typing import Any APPLY_MODE_CONFIG_KEY = "apply_mode_enabled" PERSONAL_APPLY_ENABLED_CONFIG_KEY = "personal_apply_enabled" SERVICE_ACCOUNT_APPLY_ENABLED_CONFIG_KEY = "service_account_apply_enabled" _LEGACY_SERVICE_ACCOUNT_APPLY_PRINCIPAL_ALLOWLIST_CONFIG_KEY = ( "service_account_apply_principal_allowlist" ) _TRUTHY_CONFIG_VALUES = frozenset({"1", "true", "yes", "on", "enabled"}) _LEGACY_SERVICE_ACCOUNT_APPLY_DATASET_ALLOWLIST_CONFIG_KEY = ( "service_account_apply_dataset_allowlist" ) _PROJECT_ID_RE = re.compile(r"^[a-z][a-z0-9-]{4,28}[a-z0-9]$") _DATASET_ID_RE = re.compile(r"^[A-Za-z0-9_]{1,1024}$") @dataclass(frozen=True, slots=True) class DataAccessApplyGateConfig: """Parsed provider-mutation gate stored on the data-access integration.""" apply_mode_enabled: bool personal_apply_enabled: bool service_account_apply_enabled: bool def validate_dataset_locator(project_id: str, dataset_id: str) -> None: if not _PROJECT_ID_RE.fullmatch(project_id): raise ValueError( "project_id must be a canonical GCP project id, not a " "project number, table path, or freeform resource", ) if "." in dataset_id or ":" in dataset_id or "/" in dataset_id: raise ValueError("dataset_id must name a dataset only, not a table path") if not _DATASET_ID_RE.fullmatch(dataset_id): raise ValueError("dataset_id is not a valid BigQuery dataset id") def dataset_locator_key(project_id: str, dataset_id: str) -> str: validate_dataset_locator(project_id, dataset_id) return f"{project_id}.{dataset_id}" def data_access_apply_mode_enabled(config: Mapping[str, Any]) -> bool: raw = config.get(APPLY_MODE_CONFIG_KEY) if isinstance(raw, bool): return raw if raw is None: return False return str(raw).strip().lower() in _TRUTHY_CONFIG_VALUES def merge_data_access_apply_mode_config( existing: Mapping[str, Any] | None, *, apply_mode_enabled: str | bool, personal_apply_enabled: str | bool | None = None, ) -> dict[str, Any]: """Update explicit apply switches and retire both legacy SA allowlists. ``None`` preserves the current personal gate so ordinary credential or global apply-mode projections cannot silently enable or disable personal grants. Callers must pass an explicit value to change that policy. """ if existing is None: merged: dict[str, Any] = {} elif isinstance(existing, Mapping): merged = dict(existing) else: raise ValueError("BigQuery DAX apply gate config must be an object") merged.pop(_LEGACY_SERVICE_ACCOUNT_APPLY_DATASET_ALLOWLIST_CONFIG_KEY, None) merged.pop(_LEGACY_SERVICE_ACCOUNT_APPLY_PRINCIPAL_ALLOWLIST_CONFIG_KEY, None) merged[APPLY_MODE_CONFIG_KEY] = apply_mode_enabled if personal_apply_enabled is not None: merged[PERSONAL_APPLY_ENABLED_CONFIG_KEY] = personal_apply_enabled return merged def data_access_apply_gate_config( config: Mapping[str, Any] | None, ) -> DataAccessApplyGateConfig: if config is None: config_dict: Mapping[str, Any] = {} elif isinstance(config, Mapping): config_dict = config else: raise ValueError("BigQuery DAX apply gate config must be an object") return DataAccessApplyGateConfig( apply_mode_enabled=data_access_apply_mode_enabled(config_dict), personal_apply_enabled=_config_enabled( config_dict.get(PERSONAL_APPLY_ENABLED_CONFIG_KEY), ), service_account_apply_enabled=_config_enabled( config_dict.get(SERVICE_ACCOUNT_APPLY_ENABLED_CONFIG_KEY), ), ) def _config_enabled(value: Any) -> bool: if isinstance(value, bool): return value if value is None: return False return str(value).strip().lower() in _TRUTHY_CONFIG_VALUES __all__ = [ "APPLY_MODE_CONFIG_KEY", "PERSONAL_APPLY_ENABLED_CONFIG_KEY", "SERVICE_ACCOUNT_APPLY_ENABLED_CONFIG_KEY", "DataAccessApplyGateConfig", "data_access_apply_gate_config", "data_access_apply_mode_enabled", "dataset_locator_key", "merge_data_access_apply_mode_config", "validate_dataset_locator", ]