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
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
bundle:
name: test-bundle

resources:
postgres_projects:
foo:
project_id: test-project
display_name: Test Postgres Project
permissions:
- level: CAN_USE
user_name: viewer@example.com
- level: CAN_MANAGE
group_name: data-team
- level: CAN_MANAGE
service_principal_name: f37d18cd-98a8-4db5-8112-12dd0a6bfe38
- level: CAN_MANAGE
user_name: tester@databricks.com
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"plan_version": 2,
"cli_version": "[DEV_VERSION]",
"plan": {
"resources.postgres_projects.foo": {
"action": "create",
"new_state": {
"value": {
"display_name": "Test Postgres Project",
"project_id": "test-project"
}
}
},
"resources.postgres_projects.foo.permissions": {
"depends_on": [
{
"node": "resources.postgres_projects.foo",
"label": "${resources.postgres_projects.foo.project_id}"
}
],
"action": "create",
"new_state": {
"value": {
"object_id": "/database-projects/test-project",
"permissions": [
{
"permission_level": "CAN_USE",
"user_name": "viewer@example.com"
},
{
"group_name": "data-team",
"permission_level": "CAN_MANAGE"
},
{
"permission_level": "CAN_MANAGE",
"service_principal_name": "[UUID]"
},
{
"permission_level": "CAN_MANAGE",
"user_name": "[USERNAME]"
}
]
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"cli_version": "[DEV_VERSION]",
"plan": {
"resources.postgres_projects.foo": {
"action": "create"
},
"resources.postgres_projects.foo.permissions": {
"action": "create"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"method": "PUT",
"path": "/api/2.0/permissions/database-projects/test-project",
"body": {
"access_control_list": [
{
"permission_level": "CAN_USE",
"user_name": "viewer@example.com"
},
{
"group_name": "data-team",
"permission_level": "CAN_MANAGE"
},
{
"permission_level": "CAN_MANAGE",
"service_principal_name": "[UUID]"
},
{
"permission_level": "CAN_MANAGE",
"user_name": "[USERNAME]"
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"method": "PUT",
"path": "/api/2.0/permissions/database-projects/test-project",
"body": {
"access_control_list": [
{
"permission_level": "CAN_USE",
"user_name": "viewer@example.com"
},
{
"permission_level": "CAN_MANAGE",
"service_principal_name": "[UUID]"
},
{
"group_name": "data-team",
"permission_level": "CAN_MANAGE"
},
{
"permission_level": "CAN_MANAGE",
"user_name": "[USERNAME]"
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"method": "PUT",
"path": "/api/2.0/permissions/database-projects/test-project",
"body": {
"access_control_list": [
{
"permission_level": "CAN_MANAGE",
"user_name": "[USERNAME]"
}
]
}
}

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,35 @@

>>> [CLI] bundle validate -o json
[
{
"level": "CAN_USE",
"user_name": "viewer@example.com"
},
{
"group_name": "data-team",
"level": "CAN_MANAGE"
},
{
"level": "CAN_MANAGE",
"service_principal_name": "[UUID]"
},
{
"level": "CAN_MANAGE",
"user_name": "[USERNAME]"
}
]

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

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

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

Deleting files...
Destroy complete!
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
source $TESTDIR/../../_script
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Env.RESOURCE = "postgres_projects" # for ../_script
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ var (
permissions.CAN_MANAGE: "CAN_MANAGE",
permissions.CAN_VIEW: "CAN_USE",
},
"postgres_projects": {
permissions.CAN_MANAGE: "CAN_MANAGE",
permissions.CAN_VIEW: "CAN_USE",
},
"clusters": {
// https://docs.databricks.com/aws/en/security/auth/access-control/#compute-acls
permissions.CAN_MANAGE: "CAN_MANAGE",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ var unsupportedResources = []string{
"registered_models",
"database_catalogs",
"synced_database_tables",
"postgres_projects",
"postgres_branches",
"postgres_endpoints",
}
Expand Down
15 changes: 15 additions & 0 deletions bundle/config/resources/permission.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type (
ClusterPermissionLevel string
DashboardPermissionLevel string
DatabaseInstancePermissionLevel string
DatabaseProjectPermissionLevel string
JobPermissionLevel string
MlflowExperimentPermissionLevel string
MlflowModelPermissionLevel string
Expand Down Expand Up @@ -102,6 +103,14 @@ type DatabaseInstancePermission struct {
GroupName string `json:"group_name,omitempty"`
}

type DatabaseProjectPermission struct {
Level DatabaseProjectPermissionLevel `json:"level"`

UserName string `json:"user_name,omitempty"`
ServicePrincipalName string `json:"service_principal_name,omitempty"`
GroupName string `json:"group_name,omitempty"`
}

type JobPermission struct {
Level JobPermissionLevel `json:"level"`

Expand Down Expand Up @@ -157,6 +166,7 @@ func (p AppPermission) GetAPIRequestObjectType() string { return "/
func (p ClusterPermission) GetAPIRequestObjectType() string { return "/clusters/" }
func (p DashboardPermission) GetAPIRequestObjectType() string { return "/dashboards/" }
func (p DatabaseInstancePermission) GetAPIRequestObjectType() string { return "/database-instances/" }
func (p DatabaseProjectPermission) GetAPIRequestObjectType() string { return "/database-projects/" }
func (p JobPermission) GetAPIRequestObjectType() string { return "/jobs/" }
func (p MlflowExperimentPermission) GetAPIRequestObjectType() string { return "/experiments/" }
func (p MlflowModelPermission) GetAPIRequestObjectType() string { return "/registered-models/" }
Expand Down Expand Up @@ -193,6 +203,11 @@ func (p DatabaseInstancePermission) GetUserName() string { return p.
func (p DatabaseInstancePermission) GetServicePrincipalName() string { return p.ServicePrincipalName }
func (p DatabaseInstancePermission) GetGroupName() string { return p.GroupName }

func (p DatabaseProjectPermission) GetLevel() string { return string(p.Level) }
func (p DatabaseProjectPermission) GetUserName() string { return p.UserName }
func (p DatabaseProjectPermission) GetServicePrincipalName() string { return p.ServicePrincipalName }
func (p DatabaseProjectPermission) GetGroupName() string { return p.GroupName }

func (p JobPermission) GetLevel() string { return string(p.Level) }
func (p JobPermission) GetUserName() string { return p.UserName }
func (p JobPermission) GetServicePrincipalName() string { return p.ServicePrincipalName }
Expand Down
2 changes: 2 additions & 0 deletions bundle/config/resources/postgres_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ func (c *PostgresProjectConfig) MarshalJSON() ([]byte, error) {
type PostgresProject struct {
BaseResource
PostgresProjectConfig

Permissions []DatabaseProjectPermission `json:"permissions,omitempty"`
}

func (p *PostgresProject) Exists(ctx context.Context, w *databricks.WorkspaceClient, name string) (bool, error) {
Expand Down
1 change: 1 addition & 0 deletions bundle/deploy/terraform/showplanfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var prefixToGroup = []struct{ prefix, group string }{
{"model_serving_", "model_serving_endpoints"},
{"sql_endpoint_", "sql_warehouses"},
{"database_instance_", "database_instances"},
{"postgres_project_", "postgres_projects"},
}

var grantsPrefix = []struct{ prefix, group string }{
Expand Down
7 changes: 7 additions & 0 deletions bundle/deploy/terraform/tfdyn/convert_postgres_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package tfdyn

import (
"context"
"fmt"

"github.com/databricks/cli/bundle/internal/tf/schema"
"github.com/databricks/cli/libs/dyn"
Expand Down Expand Up @@ -52,6 +53,12 @@ func (c postgresProjectConverter) Convert(ctx context.Context, key string, vin d

out.PostgresProject[key] = vout.AsAny()

// Configure permissions for this resource.
if permissions := convertPermissionsResource(ctx, vin); permissions != nil {
permissions.DatabaseProjectName = fmt.Sprintf("${databricks_postgres_project.%s.project_id}", key)
out.Permissions["postgres_project_"+key] = permissions
}

return nil
}

Expand Down
53 changes: 53 additions & 0 deletions bundle/deploy/terraform/tfdyn/convert_postgres_project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,59 @@ func TestConvertPostgresProject(t *testing.T) {
}, postgresProject)
}

func TestConvertPostgresProjectWithPermissions(t *testing.T) {
src := resources.PostgresProject{
PostgresProjectConfig: resources.PostgresProjectConfig{
ProjectId: "my-project",
ProjectSpec: postgres.ProjectSpec{
DisplayName: "My Postgres Project",
PgVersion: 17,
},
},
Permissions: []resources.DatabaseProjectPermission{
{
Level: "CAN_USE",
UserName: "user@example.com",
},
{
Level: "CAN_MANAGE",
ServicePrincipalName: "sp-name",
},
},
}

vin, err := convert.FromTyped(src, dyn.NilValue)
require.NoError(t, err)

ctx := context.Background()
out := schema.NewResources()
err = postgresProjectConverter{}.Convert(ctx, "my_postgres_project", vin, out)
require.NoError(t, err)

postgresProject := out.PostgresProject["my_postgres_project"]
assert.Equal(t, map[string]any{
"project_id": "my-project",
"spec": map[string]any{
"display_name": "My Postgres Project",
"pg_version": int64(17),
},
}, postgresProject)

assert.Equal(t, &schema.ResourcePermissions{
DatabaseProjectName: "${databricks_postgres_project.my_postgres_project.project_id}",
AccessControl: []schema.ResourcePermissionsAccessControl{
{
PermissionLevel: "CAN_USE",
UserName: "user@example.com",
},
{
PermissionLevel: "CAN_MANAGE",
ServicePrincipalName: "sp-name",
},
},
}, out.Permissions["postgres_project_my_postgres_project"])
}

func TestConvertPostgresProjectMinimal(t *testing.T) {
src := resources.PostgresProject{
PostgresProjectConfig: resources.PostgresProjectConfig{
Expand Down
1 change: 1 addition & 0 deletions bundle/direct/dresources/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ var SupportedResources = map[string]any{
"alerts.permissions": (*ResourcePermissions)(nil),
"clusters.permissions": (*ResourcePermissions)(nil),
"database_instances.permissions": (*ResourcePermissions)(nil),
"postgres_projects.permissions": (*ResourcePermissions)(nil),
"experiments.permissions": (*ResourcePermissions)(nil),
"models.permissions": (*ResourcePermissions)(nil),
"sql_warehouses.permissions": (*ResourcePermissions)(nil),
Expand Down
22 changes: 22 additions & 0 deletions bundle/direct/dresources/all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,28 @@ var testDeps = map[string]prepareWorkspace{
}, nil
},

"postgres_projects.permissions": func(client *databricks.WorkspaceClient) (any, error) {
waiter, err := client.Postgres.CreateProject(context.Background(), postgres.CreateProjectRequest{
ProjectId: "permissions-project",
})
if err != nil {
return nil, err
}
result, err := waiter.Wait(context.Background())
if err != nil {
return nil, err
}

components, _ := ParsePostgresName(result.Name)
return &PermissionsState{
ObjectID: "/database-projects/" + components.ProjectID,
Permissions: []iam.AccessControlRequest{{
PermissionLevel: "CAN_MANAGE",
UserName: "user@example.com",
}},
}, nil
},

"dashboards.permissions": func(client *databricks.WorkspaceClient) (any, error) {
ctx := context.Background()
parentPath := "/Workspace/Users/user@example.com"
Expand Down
6 changes: 6 additions & 0 deletions bundle/direct/dresources/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ func PreparePermissionsInputConfig(inputConfig any, node string) (*structvar.Str
objectIdRef = prefix + "${" + baseNode + ".endpoint_id}"
}

// Postgres projects store their hierarchical name ("projects/{project_id}") as the state ID,
// but the permissions API expects just the project_id.
if strings.HasPrefix(baseNode, "resources.postgres_projects.") {
objectIdRef = prefix + "${" + baseNode + ".project_id}"
}

return &structvar.StructVar{
Value: &PermissionsState{
ObjectID: "", // Always a reference, defined in Refs below
Expand Down
Loading
Loading