databricks-tellr 0.3.13.dev16__tar.gz → 0.3.13.dev17__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: databricks-tellr
3
- Version: 0.3.13.dev16
3
+ Version: 0.3.13.dev17
4
4
  Summary: Tellr deployment tooling for Databricks Apps
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -67,3 +67,4 @@ env:
67
67
  # Set to "0" if you want to force-disable the fast path entirely.
68
68
  - name: HUASHU_PIPELINE_ENABLED
69
69
  value: "1"
70
+ $ENCRYPTION_KEY_BLOCK
@@ -287,9 +287,9 @@ def update(
287
287
  reset_database: If True, drop and recreate the schema (tables recreated on app startup)
288
288
  client: External WorkspaceClient (optional)
289
289
  profile: Databricks CLI profile name (optional)
290
- encryption_key: Override for the legacy Fernet key to relocate into
291
- the encryption_keys table. Default: read from the deployed
292
- app.yaml. The key is no longer written to app.yaml.
290
+ encryption_key: Override for the legacy Fernet key to carry forward
291
+ into the regenerated app.yaml (boot then migrates it into the
292
+ encryption_keys table). Default: read from the deployed app.yaml.
293
293
  mlflow_tracing: Optional overrides for UC tracing env vars (same keys as ``create``).
294
294
  Values from deployment YAML are not loaded on update; use this argument or
295
295
  ``TELLR_DEPLOY_MLFLOW_*`` environment variables.
@@ -504,9 +504,9 @@ def _update_databricks(
504
504
  client: External WorkspaceClient (optional)
505
505
  profile: Databricks CLI profile name (optional)
506
506
  seed_databricks_defaults: If True, seed Databricks-specific content on startup
507
- encryption_key: Override for the legacy Fernet key to relocate into
508
- the encryption_keys table. Default: read from the deployed
509
- app.yaml. The key is no longer written to app.yaml.
507
+ encryption_key: Override for the legacy Fernet key to carry forward
508
+ into the regenerated app.yaml (boot then migrates it into the
509
+ encryption_keys table). Default: read from the deployed app.yaml.
510
510
  mlflow_tracing: Optional overrides for UC tracing placeholders in ``app.yaml``.
511
511
 
512
512
  Returns:
@@ -524,7 +524,7 @@ def _update_databricks(
524
524
  overrides=mlflow_tracing,
525
525
  )
526
526
 
527
- # Preserve the existing encryption key so we don't invalidate encrypted data
527
+ # Preserve the existing key so boot can migrate it (carry-forward).
528
528
  if not encryption_key:
529
529
  encryption_key = _read_existing_encryption_key(ws, app_file_workspace_path)
530
530
 
@@ -545,28 +545,6 @@ def _update_databricks(
545
545
  print(f" Schema '{schema_name}' reset (tables will be recreated on app startup)")
546
546
  print()
547
547
 
548
- # CRITICAL-3 migration: relocate the legacy app.yaml key into the
549
- # encryption_keys table BEFORE the new (keyless) app.yaml overwrites
550
- # it. Runs at most once per install: after it succeeds, the deployed
551
- # app.yaml has no key entry and encryption_key is None on re-runs.
552
- if encryption_key:
553
- print("Relocating encryption key into Lakebase (encryption_keys)...")
554
- app_for_grant = ws.apps.get(name=app_name)
555
- grant_client_id = _get_app_client_id(app_for_grant)
556
- if not grant_client_id:
557
- print(" Warning: no app client ID — table grant will be skipped")
558
- mig_conn, _ = _get_lakebase_connection(
559
- ws, lakebase_name, lakebase_result=lakebase_result
560
- )
561
- try:
562
- with mig_conn.cursor() as cur:
563
- _migrate_encryption_key_to_lakebase(
564
- cur, schema_name, grant_client_id, encryption_key
565
- )
566
- finally:
567
- mig_conn.close()
568
- print(" Key relocated (relocate, not rotate — no re-encryption)")
569
-
570
548
  # Generate and upload updated files
571
549
  with _staging_dir() as staging:
572
550
  _write_requirements(staging, app_version)
@@ -577,6 +555,7 @@ def _update_databricks(
577
555
  seed_databricks_defaults=seed_databricks_defaults,
578
556
  lakebase_result=lakebase_result,
579
557
  mlflow_tracing=mlflow_subs,
558
+ encryption_key=encryption_key,
580
559
  )
581
560
  _upload_files(ws, staging, app_file_workspace_path)
582
561
  print(" Files updated")
@@ -788,65 +767,6 @@ def _read_existing_encryption_key(
788
767
  return None
789
768
 
790
769
 
791
- def _migrate_encryption_key_to_lakebase(
792
- cur: Any,
793
- schema_name: str,
794
- client_id: str | None,
795
- encryption_key: str,
796
- ) -> None:
797
- """Relocate the legacy app.yaml Fernet key into <schema>.encryption_keys.
798
-
799
- SDR-4437 CRITICAL-3, deploy-time migration. Relocate, not rotate: no
800
- re-encryption of existing rows. Idempotent: re-running with the same key
801
- is a no-op. A *different* pre-existing key is a hard error — silently
802
- keeping either key would orphan the ciphertext encrypted under the other.
803
-
804
- The GRANT is unconditional: the original deployer's ALTER DEFAULT
805
- PRIVILEGES attaches to the creating role, so when a different identity
806
- runs the upgrade this explicit grant is the only one that applies.
807
- INSERT is included so the app's boot seed (insert-when-missing branch)
808
- can never be privilege-blocked.
809
-
810
- Takes an open cursor so callers control the endpoint (prod branch,
811
- devloop fork, ...). Caller owns the connection lifecycle.
812
- """
813
- validate_schema_name(schema_name)
814
- if client_id is not None:
815
- validate_client_id(client_id)
816
-
817
- # Matches the SQLAlchemy model (src/database/models/encryption_key.py):
818
- # id INTEGER PK (no autoincrement), key_value TEXT, created_at TIMESTAMP.
819
- cur.execute(
820
- f'CREATE TABLE IF NOT EXISTS "{schema_name}".encryption_keys ('
821
- "id INTEGER PRIMARY KEY, "
822
- "key_value TEXT NOT NULL, "
823
- "created_at TIMESTAMP NOT NULL)"
824
- )
825
- cur.execute(
826
- f'INSERT INTO "{schema_name}".encryption_keys (id, key_value, created_at) '
827
- "VALUES (1, %s, CURRENT_TIMESTAMP) ON CONFLICT (id) DO NOTHING",
828
- (encryption_key,),
829
- )
830
- cur.execute(
831
- f'SELECT key_value FROM "{schema_name}".encryption_keys WHERE id = 1'
832
- )
833
- row = cur.fetchone()
834
- if not row or row[0] != encryption_key:
835
- raise DeploymentError(
836
- "encryption_keys already holds a DIFFERENT key than the deployed "
837
- "app.yaml. Refusing to continue: proceeding would orphan ciphertext "
838
- "encrypted under one of the two keys. Determine which key decrypts "
839
- "the existing google_oauth_tokens/google_global_credentials rows, "
840
- "fix the encryption_keys row (or the app.yaml) to match, then "
841
- "re-run the update. See SDR-4437 PR-3 design."
842
- )
843
- if client_id:
844
- cur.execute(
845
- f'GRANT SELECT, INSERT ON "{schema_name}".encryption_keys '
846
- f'TO "{client_id}"'
847
- )
848
-
849
-
850
770
  def _load_deployment_config(config_yaml_path: str) -> dict[str, str]:
851
771
  """Load deployment settings from config/deployment.yaml-style files.
852
772
 
@@ -1367,11 +1287,14 @@ def _write_app_yaml(
1367
1287
  seed_databricks_defaults: bool = False,
1368
1288
  lakebase_result: dict[str, Any] | None = None,
1369
1289
  mlflow_tracing: dict[str, str] | None = None,
1290
+ encryption_key: str | None = None,
1370
1291
  ) -> None:
1371
1292
  """Generate app.yaml with environment variables.
1372
1293
 
1373
- The Fernet encryption key is deliberately NOT written here (SDR-4437
1374
- CRITICAL-3): the app reads/seeds it from the encryption_keys table.
1294
+ The Fernet encryption key is written ONLY when ``encryption_key`` is
1295
+ provided (the one-time legacy→table carry-forward). Boot seeds the
1296
+ encryption_keys table from it and then scrubs it from app.yaml. When
1297
+ ``encryption_key`` is None the app.yaml is keyless (steady state).
1375
1298
 
1376
1299
  Args:
1377
1300
  staging_dir: Directory to write the app.yaml file
@@ -1397,8 +1320,17 @@ def _write_app_yaml(
1397
1320
  if mlflow_tracing is None:
1398
1321
  mlflow_tracing = _mlflow_substitutions_for_app_yaml()
1399
1322
 
1323
+ if encryption_key:
1324
+ key_block = (
1325
+ " - name: GOOGLE_OAUTH_ENCRYPTION_KEY\n"
1326
+ f' value: "{encryption_key}"'
1327
+ )
1328
+ else:
1329
+ key_block = ""
1330
+
1400
1331
  template_content = _load_template("app.yaml.template")
1401
1332
  content = Template(template_content).substitute(
1333
+ ENCRYPTION_KEY_BLOCK=key_block,
1402
1334
  LAKEBASE_INSTANCE=lakebase_name,
1403
1335
  LAKEBASE_SCHEMA=schema_name,
1404
1336
  INIT_DATABASE_CALL=init_call,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: databricks-tellr
3
- Version: 0.3.13.dev16
3
+ Version: 0.3.13.dev17
4
4
  Summary: Tellr deployment tooling for Databricks Apps
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "databricks-tellr"
7
- version = "0.3.13.dev16"
7
+ version = "0.3.13.dev17"
8
8
  description = "Tellr deployment tooling for Databricks Apps"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"