Skip to content

Design: one toggle between databricks-emulator and a real workspace

Status: shipped (python/databricks-target/, CI e2e-databricks-target). A consumer’s Python — databricks-sdk, dbt-databricks, warehouse SQL — runs against either the local emulator or a real Databricks workspace, switched by one setting, with zero code edits.

This is the sibling of fabric-emulator’s fabric-target (FABRIC_TARGET=emulator|real). Contoso’s rule is “the toggle contract is installed, never restated.” While this package was unpublished a consumer could only copy hosts and PATs, which is how workspace ids and seeded secrets leak into production.

One switch: DATABRICKS_TARGET=emulator | real (default emulator).

Resolved valueemulator (zero-config defaults)real (from standard env)
Hosthttp://localhost:8447DATABRICKS_HOST (https://adb-*.azuredatabricks.net)
Credentialseeded admin PAT (DATABRICKS_DATA_DIR/admin.pat) or DATABRICKS_TOKENDATABRICKS_TOKEN (required)
SQL warehouseby name, resolved to wh-* / /sql/1.0/endpoints/{id}by name (DATABRICKS_WAREHOUSE, required) — never created here
Catalog / schemascontoso / silver / goldsame names, override with DATABRICKS_CATALOG
Secret scopecontososame name
Key Vaultazure-keyvault-emulator (https://localhost:8444)AZURE_KEY_VAULT_URL
TLS verifyoff (self-signed family certs, or plain HTTP)on — no knob turns it off
Engine attachDATABRICKS_SPARK_CONNECT_URL + optional DATABRICKS_UC_URLthe workspace is the engine
Seed secretsallowedrefused
MANAGED tablesnot supported (UC OSS EXTERNAL only)supported
Grantsnot enforced (501 until they deny)enforced

Ids are the one thing that can never match across targets — so the contract is name-based: user code holds warehouse / catalog / schema / scope display names; the resolver translates to ids per target.

Variable names, and why there are two sets

Section titled “Variable names, and why there are two sets”

Emulator-mode knobs are named DATABRICKS_EMULATOR_URL / VAULT_EMULATOR_URL; real mode reads DATABRICKS_HOST and AZURE_KEY_VAULT_URL. A consumer driving both targets from one compose file writes the production names, because real mode leaves it no choice — so emulator mode accepts them as aliases. The emulator-specific name still wins.

Resolved valuePreferredAlso accepted
Workspace hostDATABRICKS_EMULATOR_URLDATABRICKS_HOST
Key VaultVAULT_EMULATOR_URLAZURE_KEY_VAULT_URL

Real mode refuses a localhost host. A shell left over from make run must not silently talk to the emulator while believing it is production.

from databricks_target import target
t = target() # reads DATABRICKS_TARGET
w = t.workspace_client() # host + token already set
wh = t.warehouse("contoso_warehouse") # name -> id / http_path
name = t.three_part("gold", "fct_revenue_summary")

No if t.name == "emulator" in the consumer. Policy flags that are genuinely different live on the Target object:

  • t.seed_secrets_allowed — call t.refuse_seed_secrets() from the seed step
  • t.managed_tables_supported — emulator is EXTERNAL only
  • t.grants_enforced — emulator grants stay 501
  • t.engine_is_attached — emulator is false until DATABRICKS_SPARK_CONNECT_URL is set

warehouse() resolves. It does not create. Provision is the consumer’s job, the same way fabric-target.workspace() only resolves.

make e2e-databricks-target starts this binary plus Sail, resolves the emulator profile, creates a warehouse named contoso_warehouse (the consumer half), resolves it by name through the package, and runs SELECT 1. Real-target conformance is secret-gated (.github/workflows/real-databricks.yml) and is not a PR check.

  • A Databricks Runtime. Photon / DBR claims stay refused.
  • A substitute for DATABRICKS_* on the server. This package is the client resolver. The binary still reads 04-configuration.md.
  • OpenMetadata. Governance compose belongs to the consumer, as contoso-fabric-platform owns its own governance profile.