Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
f2471e4
Integrate deployment metadata service for server-side locking and state
shreyas-goenka Mar 26, 2026
9d055f4
Fix correctness bugs and improve code quality from self-review
shreyas-goenka Mar 30, 2026
342fef8
Refactor to SDK-style tempdms package and unify deploy/destroy flows
shreyas-goenka Mar 30, 2026
29f5670
Fix query parameter handling for deployment metadata service API
shreyas-goenka Mar 31, 2026
aa11d7c
Fix remaining issues: enum naming, redundant param, add acceptance test
shreyas-goenka Mar 31, 2026
c21aee6
Update acceptance test to print all metadata service requests
shreyas-goenka Mar 31, 2026
1621bc2
Fix error masking and input validation from self-review
shreyas-goenka Mar 31, 2026
b1a9a0a
Add acceptance test golden files and fix SDK compatibility
shreyas-goenka Mar 31, 2026
8756548
Use string enums, report failed operations, and refactor lock acquisi…
shreyas-goenka Mar 31, 2026
8117d81
Inject liteswap traffic ID header when DATABRICKS_LITESWAP_ID is set
shreyas-goenka Apr 1, 2026
d648a9e
Remove unused liteswap env helper
shreyas-goenka Apr 1, 2026
fb92d36
Merge remote-tracking branch 'origin' into shreyas-goenka/deployment-…
shreyas-goenka Apr 7, 2026
3108d85
Delete deployment on destroy, use env.Get for liteswap, remove unused…
shreyas-goenka Apr 7, 2026
aad93ec
Move DeleteDeployment into cleanup closure, expand operation action m…
shreyas-goenka Apr 7, 2026
9292597
Add DeploymentLock and ResourceState interfaces for backend-agnostic …
shreyas-goenka Apr 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions acceptance/bundle/deploy/metadata-service/databricks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
bundle:
name: metadata-service-test

resources:
jobs:
test_job:
name: test-job
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
bundle:
name: metadata-service-error-test

resources:
jobs:
test_job:
name: test-job

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@

>>> musterr [CLI] bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/metadata-service-error-test/default/files...
Deploying resources...
Error: cannot create resources.jobs.test_job: Invalid job configuration. (400 INVALID_PARAMETER_VALUE)

Endpoint: POST [DATABRICKS_URL]/api/2.2/jobs/create
HTTP Status: 400 Bad Request
API error_code: INVALID_PARAMETER_VALUE
API message: Invalid job configuration.

Updating deployment state...

>>> print_requests.py --get //bundle
{
"method": "POST",
"path": "/api/2.0/bundle/deployments",
"q": {
"deployment_id": "[UUID]"
},
"body": {
"target_name": "default"
}
}
{
"method": "GET",
"path": "/api/2.0/bundle/deployments/[UUID]"
}
{
"method": "POST",
"path": "/api/2.0/bundle/deployments/[UUID]/versions",
"q": {
"version_id": "1"
},
"body": {
"cli_version": "[DEV_VERSION]",
"version_type": "VERSION_TYPE_DEPLOY",
"target_name": "default"
}
}
{
"method": "POST",
"path": "/api/2.0/bundle/deployments/[UUID]/versions/1/operations",
"q": {
"resource_key": "resources.jobs.test_job"
},
"body": {
"resource_key": "resources.jobs.test_job",
"action_type": "OPERATION_ACTION_TYPE_CREATE",
"status": "OPERATION_STATUS_FAILED",
"error_message": "Invalid job configuration."
}
}
{
"method": "POST",
"path": "/api/2.0/bundle/deployments/[UUID]/versions/1/complete",
"body": {
"name": "deployments/[UUID]/versions/1",
"completion_reason": "VERSION_COMPLETE_FAILURE"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Deploy with the metadata service enabled, expecting a resource creation failure.
trace musterr $CLI bundle deploy

# Print the metadata service requests to verify the failed operation is reported.
trace print_requests.py --get //bundle
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"]
EnvMatrix.DATABRICKS_BUNDLE_MANAGED_STATE = ["true"]
RecordRequests = true

[[Server]]
Pattern = "POST /api/2.2/jobs/create"
Response.StatusCode = 400
Response.Body = '{"error_code": "INVALID_PARAMETER_VALUE", "message": "Invalid job configuration."}'
6 changes: 6 additions & 0 deletions acceptance/bundle/deploy/metadata-service/out.test.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

117 changes: 117 additions & 0 deletions acceptance/bundle/deploy/metadata-service/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@

>>> [CLI] bundle deploy
Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/metadata-service-test/default/files...
Deploying resources...
Updating deployment state...
Deployment complete!

>>> print_requests.py --get //bundle
{
"method": "POST",
"path": "/api/2.0/bundle/deployments",
"q": {
"deployment_id": "[UUID]"
},
"body": {
"target_name": "default"
}
}
{
"method": "GET",
"path": "/api/2.0/bundle/deployments/[UUID]"
}
{
"method": "POST",
"path": "/api/2.0/bundle/deployments/[UUID]/versions",
"q": {
"version_id": "1"
},
"body": {
"cli_version": "[DEV_VERSION]",
"version_type": "VERSION_TYPE_DEPLOY",
"target_name": "default"
}
}
{
"method": "POST",
"path": "/api/2.0/bundle/deployments/[UUID]/versions/1/operations",
"q": {
"resource_key": "resources.jobs.test_job"
},
"body": {
"resource_key": "resources.jobs.test_job",
"action_type": "OPERATION_ACTION_TYPE_CREATE",
"resource_id": "[NUMID]",
"status": "OPERATION_STATUS_SUCCEEDED"
}
}
{
"method": "POST",
"path": "/api/2.0/bundle/deployments/[UUID]/versions/1/complete",
"body": {
"name": "deployments/[UUID]/versions/1",
"completion_reason": "VERSION_COMPLETE_SUCCESS"
}
}

>>> [CLI] bundle destroy --auto-approve
The following resources will be deleted:
delete resources.jobs.test_job

All files and directories at the following location will be deleted: /Workspace/Users/[USERNAME]/.bundle/metadata-service-test/default

Deleting files...
Destroy complete!

>>> print_requests.py --get //bundle
{
"method": "POST",
"path": "/api/2.0/bundle/deployments",
"q": {
"deployment_id": "[UUID]"
},
"body": {
"target_name": "default"
}
}
{
"method": "GET",
"path": "/api/2.0/bundle/deployments/[UUID]"
}
{
"method": "POST",
"path": "/api/2.0/bundle/deployments/[UUID]/versions",
"q": {
"version_id": "1"
},
"body": {
"cli_version": "[DEV_VERSION]",
"version_type": "VERSION_TYPE_DESTROY",
"target_name": "default"
}
}
{
"method": "POST",
"path": "/api/2.0/bundle/deployments/[UUID]/versions/1/operations",
"q": {
"resource_key": "resources.jobs.test_job"
},
"body": {
"resource_key": "resources.jobs.test_job",
"action_type": "OPERATION_ACTION_TYPE_DELETE",
"resource_id": "[NUMID]",
"status": "OPERATION_STATUS_SUCCEEDED"
}
}
{
"method": "POST",
"path": "/api/2.0/bundle/deployments/[UUID]/versions/1/complete",
"body": {
"name": "deployments/[UUID]/versions/1",
"completion_reason": "VERSION_COMPLETE_SUCCESS"
}
}
{
"method": "DELETE",
"path": "/api/2.0/bundle/deployments/[UUID]"
}
11 changes: 11 additions & 0 deletions acceptance/bundle/deploy/metadata-service/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Deploy with the metadata service enabled.
trace $CLI bundle deploy

# Print all metadata service requests made during deploy.
trace print_requests.py --get //bundle

# Destroy with the metadata service enabled.
trace $CLI bundle destroy --auto-approve

# Print all metadata service requests made during destroy.
trace print_requests.py --get //bundle
3 changes: 3 additions & 0 deletions acceptance/bundle/deploy/metadata-service/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["direct"]
EnvMatrix.DATABRICKS_BUNDLE_MANAGED_STATE = ["true"]
RecordRequests = true
6 changes: 3 additions & 3 deletions bundle/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,10 +225,10 @@ func TryLoad(ctx context.Context) *Bundle {
return b
}

func (b *Bundle) WorkspaceClientE() (*databricks.WorkspaceClient, error) {
func (b *Bundle) WorkspaceClientE(ctx context.Context) (*databricks.WorkspaceClient, error) {
b.clientOnce.Do(func() {
var err error
b.client, err = b.Config.Workspace.Client()
b.client, err = b.Config.Workspace.Client(ctx)
if err != nil {
b.clientErr = fmt.Errorf("cannot resolve bundle auth configuration: %w", err)
}
Expand All @@ -238,7 +238,7 @@ func (b *Bundle) WorkspaceClientE() (*databricks.WorkspaceClient, error) {
}

func (b *Bundle) WorkspaceClient() *databricks.WorkspaceClient {
client, err := b.WorkspaceClientE()
client, err := b.WorkspaceClientE(context.TODO())
if err != nil {
panic(err)
}
Expand Down
7 changes: 4 additions & 3 deletions bundle/bundle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,20 +184,21 @@ func TestClearWorkspaceClient(t *testing.T) {
b.Config.Workspace.Host = "https://nonexistent.example.com"
b.Config.Workspace.Profile = "profile-A"

_, err1 := b.WorkspaceClientE()
ctx := t.Context()
_, err1 := b.WorkspaceClientE(ctx)
require.Error(t, err1)
assert.Contains(t, err1.Error(), "profile-A")

// Without retry, second call returns the same cached error (same object).
_, err1b := b.WorkspaceClientE()
_, err1b := b.WorkspaceClientE(ctx)
assert.Same(t, err1, err1b, "expected same cached error without retry")

// After retry, change the profile to "profile-B" and call again.
// If retry didn't re-execute, the error would still mention "profile-A".
b.ClearWorkspaceClient()
b.Config.Workspace.Profile = "profile-B"

_, err2 := b.WorkspaceClientE()
_, err2 := b.WorkspaceClientE(ctx)
require.Error(t, err2)
assert.Contains(t, err2.Error(), "profile-B", "expected re-execution to pick up new profile")
assert.NotContains(t, err2.Error(), "profile-A", "stale cached error should not appear")
Expand Down
30 changes: 29 additions & 1 deletion bundle/config/workspace.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package config

import (
"context"
"net/http"
"os"
"path/filepath"

"github.com/databricks/cli/libs/auth"
"github.com/databricks/cli/libs/databrickscfg"
"github.com/databricks/cli/libs/env"
"github.com/databricks/databricks-sdk-go"
"github.com/databricks/databricks-sdk-go/config"
"github.com/databricks/databricks-sdk-go/marshal"
Expand Down Expand Up @@ -156,7 +159,7 @@ func (w *Workspace) NormalizeHostURL() {
}
}

func (w *Workspace) Client() (*databricks.WorkspaceClient, error) {
func (w *Workspace) Client(ctx context.Context) (*databricks.WorkspaceClient, error) {
// Extract query parameters (?o=, ?a=) from the host URL before building
// the SDK config. This ensures workspace_id and account_id are available
// for profile resolution during EnsureResolved().
Expand Down Expand Up @@ -193,9 +196,34 @@ func (w *Workspace) Client() (*databricks.WorkspaceClient, error) {
}
}

// If DATABRICKS_LITESWAP_ID is set, wrap the transport to inject the
// x-databricks-traffic-id header for routing to the liteswap instance.
if liteswapID := env.Get(ctx, "DATABRICKS_LITESWAP_ID"); liteswapID != "" {
inner := cfg.HTTPTransport
if inner == nil {
inner = http.DefaultTransport
}
cfg.HTTPTransport = &liteswapTransport{
inner: inner,
trafficID: "testenv://liteswap/" + liteswapID,
}
}

return databricks.NewWorkspaceClient((*databricks.Config)(cfg))
}

// liteswapTransport injects the x-databricks-traffic-id header to route
// requests to a liteswap service instance.
type liteswapTransport struct {
inner http.RoundTripper
trafficID string
}

func (t *liteswapTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Set("x-databricks-traffic-id", t.trafficID)
return t.inner.RoundTrip(req)
}

func init() {
arg0 := os.Args[0]

Expand Down
Loading
Loading