Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions NEXT_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

### Bundles

* Allow bundles with `apps` resources to have a top-level `run_as` identity configured. Apps do not support `run_as` via the API and are simply skipped; other resources (jobs, pipelines, etc.) continue to have `run_as` applied as before.

### Dependency updates

### API Changes
1 change: 1 addition & 0 deletions acceptance/bundle/run_as/app_different/app/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Minimal app stub for acceptance testing
12 changes: 12 additions & 0 deletions acceptance/bundle/run_as/app_different/databricks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
bundle:
name: "run_as"

run_as:
service_principal_name: "my_service_principal"

resources:
apps:
foo:
name: "my_app"
description: "An app with a differing run_as identity"
source_code_path: ./app
3 changes: 3 additions & 0 deletions acceptance/bundle/run_as/app_different/out.test.toml

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

7 changes: 7 additions & 0 deletions acceptance/bundle/run_as/app_different/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Name: run_as
Target: default
Workspace:
User: [USERNAME]
Path: /Workspace/Users/[USERNAME]/.bundle/run_as/default

Validation OK!
1 change: 1 addition & 0 deletions acceptance/bundle/run_as/app_different/script
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$CLI bundle validate
10 changes: 0 additions & 10 deletions bundle/config/mutator/resourcemutator/run_as.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,16 +116,6 @@ func validateRunAs(b *bundle.Bundle) diag.Diagnostics {
}
}

// Apps do not support run_as in the API.
if len(b.Config.Resources.Apps) > 0 {
diags = diags.Extend(reportRunAsNotSupported(
"apps",
b.Config.GetLocation("resources.apps"),
b.Config.Workspace.CurrentUser.UserName,
identity,
))
}

return diags
}

Expand Down
172 changes: 172 additions & 0 deletions bundle/config/mutator/resourcemutator/run_as_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/databricks/cli/bundle/config/resources"
"github.com/databricks/cli/libs/dyn"
"github.com/databricks/cli/libs/dyn/convert"
"github.com/databricks/databricks-sdk-go/service/apps"
"github.com/databricks/databricks-sdk-go/service/iam"
"github.com/databricks/databricks-sdk-go/service/jobs"
"github.com/databricks/databricks-sdk-go/service/sql"
Expand Down Expand Up @@ -169,6 +170,7 @@ func TestRunAsWorksForAllowedResources(t *testing.T) {
// they are not on the allow list below.
var allowList = []string{
"alerts",
"apps",
"catalogs",
"clusters",
"dashboards",
Expand Down Expand Up @@ -301,3 +303,173 @@ func TestRunAsNoErrorForSupportedResources(t *testing.T) {
require.NoError(t, diags.Error())
}
}

func TestRunAsAppNotMutated(t *testing.T) {
b := &bundle.Bundle{
Config: config.Root{
Workspace: config.Workspace{
CurrentUser: &config.User{
User: &iam.User{UserName: "alice"},
},
},
RunAs: &jobs.JobRunAs{UserName: "bob"},
Resources: config.Resources{
Apps: map[string]*resources.App{
"my_app": {
App: apps.App{
Name: "my_app",
Description: "desc",
},
SourceCodePath: "./src",
},
},
},
},
}

diags := bundle.Apply(t.Context(), b, SetRunAs())
assert.NoError(t, diags.Error())

app := b.Config.Resources.Apps["my_app"]
assert.Equal(t, "my_app", app.Name)
assert.Equal(t, "./src", app.SourceCodePath)
assert.Equal(t, "desc", app.Description)
}

func TestRunAsNoOpForApps(t *testing.T) {
tests := []struct {
name string
runAs *jobs.JobRunAs
}{
{
name: "user identity",
runAs: &jobs.JobRunAs{UserName: "bob"},
},
{
name: "service principal",
runAs: &jobs.JobRunAs{ServicePrincipalName: "sp-acme"},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
b := &bundle.Bundle{
Config: config.Root{
Workspace: config.Workspace{
CurrentUser: &config.User{
User: &iam.User{UserName: "alice"},
},
},
RunAs: tc.runAs,
Resources: config.Resources{
Apps: map[string]*resources.App{
"my_app": {App: apps.App{Name: "my_app"}},
},
},
},
}

diags := bundle.Apply(t.Context(), b, SetRunAs())
assert.NoError(t, diags.Error())
})
}
}

func TestRunAsNoRunAs_AppUnchanged(t *testing.T) {
b := &bundle.Bundle{
Config: config.Root{
Workspace: config.Workspace{
CurrentUser: &config.User{
User: &iam.User{UserName: "alice"},
},
},
Resources: config.Resources{
Apps: map[string]*resources.App{
"my_app": {App: apps.App{Name: "my_app"}},
},
},
},
}

diags := bundle.Apply(t.Context(), b, SetRunAs())
assert.Nil(t, diags)
assert.Equal(t, "my_app", b.Config.Resources.Apps["my_app"].Name)
}

func TestRunAsAppAndJobTogether(t *testing.T) {
b := &bundle.Bundle{
Config: config.Root{
Workspace: config.Workspace{
CurrentUser: &config.User{
User: &iam.User{UserName: "alice"},
},
},
RunAs: &jobs.JobRunAs{UserName: "bob"},
Resources: config.Resources{
Apps: map[string]*resources.App{
"my_app": {App: apps.App{Name: "my_app"}},
},
Jobs: map[string]*resources.Job{
"my_job": {
JobSettings: jobs.JobSettings{Name: "my_job"},
},
},
},
},
}

diags := bundle.Apply(t.Context(), b, SetRunAs())
require.NoError(t, diags.Error())
assert.Equal(t, "bob", b.Config.Resources.Jobs["my_job"].RunAs.UserName)
assert.Equal(t, "my_app", b.Config.Resources.Apps["my_app"].Name)
}

func TestRunAsAppWithDeniedResourceStillErrors(t *testing.T) {
b := &bundle.Bundle{
Config: config.Root{
Workspace: config.Workspace{
CurrentUser: &config.User{
User: &iam.User{UserName: "alice"},
},
},
RunAs: &jobs.JobRunAs{UserName: "bob"},
Resources: config.Resources{
Apps: map[string]*resources.App{
"my_app": {App: apps.App{Name: "my_app"}},
},
ModelServingEndpoints: map[string]*resources.ModelServingEndpoint{
"my_endpoint": {},
},
},
},
}

diags := bundle.Apply(t.Context(), b, SetRunAs())
require.True(t, diags.HasError())
assert.Contains(t, diags.Error().Error(), "model_serving_endpoints do not support a setting a run_as user")
assert.NotContains(t, diags.Error().Error(), "apps do not support")
}

func TestRunAsAppSameIdentity_NoError(t *testing.T) {
b := &bundle.Bundle{
Config: config.Root{
Workspace: config.Workspace{
CurrentUser: &config.User{
User: &iam.User{UserName: "alice"},
},
},
RunAs: &jobs.JobRunAs{UserName: "alice"},
Resources: config.Resources{
Apps: map[string]*resources.App{
"my_app": {App: apps.App{Name: "my_app"}},
},
ModelServingEndpoints: map[string]*resources.ModelServingEndpoint{
"my_endpoint": {},
},
},
},
}

diags := bundle.Apply(t.Context(), b, SetRunAs())
assert.NoError(t, diags.Error())
}