databricks-tellr 0.3.13.dev17__tar.gz → 0.3.13.dev19__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.
- {databricks_tellr-0.3.13.dev17 → databricks_tellr-0.3.13.dev19}/PKG-INFO +1 -1
- {databricks_tellr-0.3.13.dev17 → databricks_tellr-0.3.13.dev19}/databricks_tellr/_templates/app.yaml.template +0 -1
- {databricks_tellr-0.3.13.dev17 → databricks_tellr-0.3.13.dev19}/databricks_tellr/deploy.py +90 -22
- {databricks_tellr-0.3.13.dev17 → databricks_tellr-0.3.13.dev19}/databricks_tellr.egg-info/PKG-INFO +1 -1
- {databricks_tellr-0.3.13.dev17 → databricks_tellr-0.3.13.dev19}/pyproject.toml +1 -1
- {databricks_tellr-0.3.13.dev17 → databricks_tellr-0.3.13.dev19}/README.md +0 -0
- {databricks_tellr-0.3.13.dev17 → databricks_tellr-0.3.13.dev19}/databricks_tellr/__init__.py +0 -0
- {databricks_tellr-0.3.13.dev17 → databricks_tellr-0.3.13.dev19}/databricks_tellr/_templates/requirements.txt.template +0 -0
- {databricks_tellr-0.3.13.dev17 → databricks_tellr-0.3.13.dev19}/databricks_tellr/identifiers.py +0 -0
- {databricks_tellr-0.3.13.dev17 → databricks_tellr-0.3.13.dev19}/databricks_tellr.egg-info/SOURCES.txt +0 -0
- {databricks_tellr-0.3.13.dev17 → databricks_tellr-0.3.13.dev19}/databricks_tellr.egg-info/dependency_links.txt +0 -0
- {databricks_tellr-0.3.13.dev17 → databricks_tellr-0.3.13.dev19}/databricks_tellr.egg-info/requires.txt +0 -0
- {databricks_tellr-0.3.13.dev17 → databricks_tellr-0.3.13.dev19}/databricks_tellr.egg-info/top_level.txt +0 -0
- {databricks_tellr-0.3.13.dev17 → databricks_tellr-0.3.13.dev19}/setup.cfg +0 -0
|
@@ -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
|
|
291
|
-
|
|
292
|
-
|
|
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.
|
|
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
|
|
508
|
-
|
|
509
|
-
|
|
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.
|
|
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 key so
|
|
527
|
+
# Preserve the existing encryption key so we don't invalidate encrypted data
|
|
528
528
|
if not encryption_key:
|
|
529
529
|
encryption_key = _read_existing_encryption_key(ws, app_file_workspace_path)
|
|
530
530
|
|
|
@@ -545,6 +545,28 @@ 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
|
+
|
|
548
570
|
# Generate and upload updated files
|
|
549
571
|
with _staging_dir() as staging:
|
|
550
572
|
_write_requirements(staging, app_version)
|
|
@@ -555,7 +577,6 @@ def _update_databricks(
|
|
|
555
577
|
seed_databricks_defaults=seed_databricks_defaults,
|
|
556
578
|
lakebase_result=lakebase_result,
|
|
557
579
|
mlflow_tracing=mlflow_subs,
|
|
558
|
-
encryption_key=encryption_key,
|
|
559
580
|
)
|
|
560
581
|
_upload_files(ws, staging, app_file_workspace_path)
|
|
561
582
|
print(" Files updated")
|
|
@@ -767,6 +788,65 @@ def _read_existing_encryption_key(
|
|
|
767
788
|
return None
|
|
768
789
|
|
|
769
790
|
|
|
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
|
+
|
|
770
850
|
def _load_deployment_config(config_yaml_path: str) -> dict[str, str]:
|
|
771
851
|
"""Load deployment settings from config/deployment.yaml-style files.
|
|
772
852
|
|
|
@@ -1287,14 +1367,11 @@ def _write_app_yaml(
|
|
|
1287
1367
|
seed_databricks_defaults: bool = False,
|
|
1288
1368
|
lakebase_result: dict[str, Any] | None = None,
|
|
1289
1369
|
mlflow_tracing: dict[str, str] | None = None,
|
|
1290
|
-
encryption_key: str | None = None,
|
|
1291
1370
|
) -> None:
|
|
1292
1371
|
"""Generate app.yaml with environment variables.
|
|
1293
1372
|
|
|
1294
|
-
The Fernet encryption key is
|
|
1295
|
-
|
|
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).
|
|
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.
|
|
1298
1375
|
|
|
1299
1376
|
Args:
|
|
1300
1377
|
staging_dir: Directory to write the app.yaml file
|
|
@@ -1320,17 +1397,8 @@ def _write_app_yaml(
|
|
|
1320
1397
|
if mlflow_tracing is None:
|
|
1321
1398
|
mlflow_tracing = _mlflow_substitutions_for_app_yaml()
|
|
1322
1399
|
|
|
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
|
-
|
|
1331
1400
|
template_content = _load_template("app.yaml.template")
|
|
1332
1401
|
content = Template(template_content).substitute(
|
|
1333
|
-
ENCRYPTION_KEY_BLOCK=key_block,
|
|
1334
1402
|
LAKEBASE_INSTANCE=lakebase_name,
|
|
1335
1403
|
LAKEBASE_SCHEMA=schema_name,
|
|
1336
1404
|
INIT_DATABASE_CALL=init_call,
|
|
File without changes
|
{databricks_tellr-0.3.13.dev17 → databricks_tellr-0.3.13.dev19}/databricks_tellr/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{databricks_tellr-0.3.13.dev17 → databricks_tellr-0.3.13.dev19}/databricks_tellr/identifiers.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|