Add permissions block to each resource (#264)

Example:

```yaml
resources:
  jobs:
    my_job:
      name: "[${bundle.environment}] My job"
      permissions:
        - level: CAN_VIEW
          group_name: users
```
This commit is contained in:
Pieter Noordhuis 2023-03-21 10:58:16 +01:00 committed by GitHub
parent 58563b1ea9
commit 66ca9ec266
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 218 additions and 34 deletions

View File

@ -3,7 +3,8 @@ package resources
import "github.com/databricks/databricks-sdk-go/service/jobs" import "github.com/databricks/databricks-sdk-go/service/jobs"
type Job struct { type Job struct {
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
Permissions []Permission `json:"permissions,omitempty"`
*jobs.JobSettings *jobs.JobSettings
} }

View File

@ -3,5 +3,7 @@ package resources
import "github.com/databricks/databricks-sdk-go/service/mlflow" import "github.com/databricks/databricks-sdk-go/service/mlflow"
type MlflowExperiment struct { type MlflowExperiment struct {
Permissions []Permission `json:"permissions,omitempty"`
*mlflow.Experiment *mlflow.Experiment
} }

View File

@ -3,5 +3,7 @@ package resources
import "github.com/databricks/databricks-sdk-go/service/mlflow" import "github.com/databricks/databricks-sdk-go/service/mlflow"
type MlflowModel struct { type MlflowModel struct {
Permissions []Permission `json:"permissions,omitempty"`
*mlflow.RegisteredModel *mlflow.RegisteredModel
} }

View File

@ -0,0 +1,11 @@
package resources
// Permission holds the permission level setting for a single principal.
// Multiple of these can be defined on any resource.
type Permission struct {
Level string `json:"level"`
UserName string `json:"user_name,omitempty"`
ServicePrincipalName string `json:"service_principal_name,omitempty"`
GroupName string `json:"group_name,omitempty"`
}

View File

@ -3,7 +3,8 @@ package resources
import "github.com/databricks/databricks-sdk-go/service/pipelines" import "github.com/databricks/databricks-sdk-go/service/pipelines"
type Pipeline struct { type Pipeline struct {
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
Permissions []Permission `json:"permissions,omitempty"`
*pipelines.PipelineSpec *pipelines.PipelineSpec
} }

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"github.com/databricks/bricks/bundle/config" "github.com/databricks/bricks/bundle/config"
"github.com/databricks/bricks/bundle/config/resources"
"github.com/databricks/bricks/bundle/internal/tf/schema" "github.com/databricks/bricks/bundle/internal/tf/schema"
tfjson "github.com/hashicorp/terraform-json" tfjson "github.com/hashicorp/terraform-json"
) )
@ -14,6 +15,35 @@ func conv(from any, to any) {
json.Unmarshal(buf, &to) json.Unmarshal(buf, &to)
} }
func convPermissions(acl []resources.Permission) *schema.ResourcePermissions {
if len(acl) == 0 {
return nil
}
resource := schema.ResourcePermissions{}
for _, ac := range acl {
resource.AccessControl = append(resource.AccessControl, convPermission(ac))
}
return &resource
}
func convPermission(ac resources.Permission) schema.ResourcePermissionsAccessControl {
dst := schema.ResourcePermissionsAccessControl{
PermissionLevel: ac.Level,
}
if ac.UserName != "" {
dst.UserName = ac.UserName
}
if ac.GroupName != "" {
dst.GroupName = ac.GroupName
}
if ac.ServicePrincipalName != "" {
dst.ServicePrincipalName = ac.ServicePrincipalName
}
return dst
}
// BundleToTerraform converts resources in a bundle configuration // BundleToTerraform converts resources in a bundle configuration
// to the equivalent Terraform JSON representation. // to the equivalent Terraform JSON representation.
// //
@ -29,68 +59,96 @@ func BundleToTerraform(config *config.Root) *schema.Root {
var dst schema.ResourceJob var dst schema.ResourceJob
conv(src, &dst) conv(src, &dst)
for _, v := range src.Tasks { if src.JobSettings != nil {
var t schema.ResourceJobTask for _, v := range src.Tasks {
conv(v, &t) var t schema.ResourceJobTask
conv(v, &t)
for _, v_ := range v.Libraries { for _, v_ := range v.Libraries {
var l schema.ResourceJobTaskLibrary var l schema.ResourceJobTaskLibrary
conv(v_, &l) conv(v_, &l)
t.Library = append(t.Library, l) t.Library = append(t.Library, l)
}
dst.Task = append(dst.Task, t)
} }
dst.Task = append(dst.Task, t) for _, v := range src.JobClusters {
} var t schema.ResourceJobJobCluster
conv(v, &t)
dst.JobCluster = append(dst.JobCluster, t)
}
for _, v := range src.JobClusters { // Unblock downstream work. To be addressed more generally later.
var t schema.ResourceJobJobCluster if git := src.GitSource; git != nil {
conv(v, &t) dst.GitSource = &schema.ResourceJobGitSource{
dst.JobCluster = append(dst.JobCluster, t) Url: git.GitUrl,
} Branch: git.GitBranch,
Commit: git.GitCommit,
// Unblock downstream work. To be addressed more generally later. Provider: string(git.GitProvider),
if git := src.GitSource; git != nil { Tag: git.GitTag,
dst.GitSource = &schema.ResourceJobGitSource{ }
Url: git.GitUrl,
Branch: git.GitBranch,
Commit: git.GitCommit,
Provider: string(git.GitProvider),
Tag: git.GitTag,
} }
} }
tfroot.Resource.Job[k] = &dst tfroot.Resource.Job[k] = &dst
// Configure permissions for this resource.
if rp := convPermissions(src.Permissions); rp != nil {
rp.JobId = fmt.Sprintf("${databricks_job.%s.id}", k)
tfroot.Resource.Permissions["job_"+k] = rp
}
} }
for k, src := range config.Resources.Pipelines { for k, src := range config.Resources.Pipelines {
var dst schema.ResourcePipeline var dst schema.ResourcePipeline
conv(src, &dst) conv(src, &dst)
for _, v := range src.Libraries { if src.PipelineSpec != nil {
var l schema.ResourcePipelineLibrary for _, v := range src.Libraries {
conv(v, &l) var l schema.ResourcePipelineLibrary
dst.Library = append(dst.Library, l) conv(v, &l)
} dst.Library = append(dst.Library, l)
}
for _, v := range src.Clusters { for _, v := range src.Clusters {
var l schema.ResourcePipelineCluster var l schema.ResourcePipelineCluster
conv(v, &l) conv(v, &l)
dst.Cluster = append(dst.Cluster, l) dst.Cluster = append(dst.Cluster, l)
}
} }
tfroot.Resource.Pipeline[k] = &dst tfroot.Resource.Pipeline[k] = &dst
// Configure permissions for this resource.
if rp := convPermissions(src.Permissions); rp != nil {
rp.PipelineId = fmt.Sprintf("${databricks_pipeline.%s.id}", k)
tfroot.Resource.Permissions["pipeline_"+k] = rp
}
} }
for k, src := range config.Resources.Models { for k, src := range config.Resources.Models {
var dst schema.ResourceMlflowModel var dst schema.ResourceMlflowModel
conv(src, &dst) conv(src, &dst)
tfroot.Resource.MlflowModel[k] = &dst tfroot.Resource.MlflowModel[k] = &dst
// Configure permissions for this resource.
if rp := convPermissions(src.Permissions); rp != nil {
rp.RegisteredModelId = fmt.Sprintf("${databricks_mlflow_model.%s.registered_model_id}", k)
tfroot.Resource.Permissions["mlflow_model_"+k] = rp
}
} }
for k, src := range config.Resources.Experiments { for k, src := range config.Resources.Experiments {
var dst schema.ResourceMlflowExperiment var dst schema.ResourceMlflowExperiment
conv(src, &dst) conv(src, &dst)
tfroot.Resource.MlflowExperiment[k] = &dst tfroot.Resource.MlflowExperiment[k] = &dst
// Configure permissions for this resource.
if rp := convPermissions(src.Permissions); rp != nil {
rp.ExperimentId = fmt.Sprintf("${databricks_mlflow_experiment.%s.id}", k)
tfroot.Resource.Permissions["mlflow_experiment_"+k] = rp
}
} }
return tfroot return tfroot

View File

@ -47,6 +47,33 @@ func TestConvertJob(t *testing.T) {
assert.Nil(t, out.Data) assert.Nil(t, out.Data)
} }
func TestConvertJobPermissions(t *testing.T) {
var src = resources.Job{
Permissions: []resources.Permission{
{
Level: "CAN_VIEW",
UserName: "jane@doe.com",
},
},
}
var config = config.Root{
Resources: config.Resources{
Jobs: map[string]*resources.Job{
"my_job": &src,
},
},
}
out := BundleToTerraform(&config)
assert.NotEmpty(t, out.Resource.Permissions["job_my_job"].JobId)
assert.Len(t, out.Resource.Permissions["job_my_job"].AccessControl, 1)
p := out.Resource.Permissions["job_my_job"].AccessControl[0]
assert.Equal(t, "jane@doe.com", p.UserName)
assert.Equal(t, "CAN_VIEW", p.PermissionLevel)
}
func TestConvertJobTaskLibraries(t *testing.T) { func TestConvertJobTaskLibraries(t *testing.T) {
var src = resources.Job{ var src = resources.Job{
JobSettings: &jobs.JobSettings{ JobSettings: &jobs.JobSettings{
@ -81,6 +108,33 @@ func TestConvertJobTaskLibraries(t *testing.T) {
assert.Equal(t, "mlflow", out.Resource.Job["my_job"].Task[0].Library[0].Pypi.Package) assert.Equal(t, "mlflow", out.Resource.Job["my_job"].Task[0].Library[0].Pypi.Package)
} }
func TestConvertPipelinePermissions(t *testing.T) {
var src = resources.Pipeline{
Permissions: []resources.Permission{
{
Level: "CAN_VIEW",
UserName: "jane@doe.com",
},
},
}
var config = config.Root{
Resources: config.Resources{
Pipelines: map[string]*resources.Pipeline{
"my_pipeline": &src,
},
},
}
out := BundleToTerraform(&config)
assert.NotEmpty(t, out.Resource.Permissions["pipeline_my_pipeline"].PipelineId)
assert.Len(t, out.Resource.Permissions["pipeline_my_pipeline"].AccessControl, 1)
p := out.Resource.Permissions["pipeline_my_pipeline"].AccessControl[0]
assert.Equal(t, "jane@doe.com", p.UserName)
assert.Equal(t, "CAN_VIEW", p.PermissionLevel)
}
func TestConvertModel(t *testing.T) { func TestConvertModel(t *testing.T) {
var src = resources.MlflowModel{ var src = resources.MlflowModel{
RegisteredModel: &mlflow.RegisteredModel{ RegisteredModel: &mlflow.RegisteredModel{
@ -118,6 +172,33 @@ func TestConvertModel(t *testing.T) {
assert.Nil(t, out.Data) assert.Nil(t, out.Data)
} }
func TestConvertModelPermissions(t *testing.T) {
var src = resources.MlflowModel{
Permissions: []resources.Permission{
{
Level: "CAN_READ",
UserName: "jane@doe.com",
},
},
}
var config = config.Root{
Resources: config.Resources{
Models: map[string]*resources.MlflowModel{
"my_model": &src,
},
},
}
out := BundleToTerraform(&config)
assert.NotEmpty(t, out.Resource.Permissions["mlflow_model_my_model"].RegisteredModelId)
assert.Len(t, out.Resource.Permissions["mlflow_model_my_model"].AccessControl, 1)
p := out.Resource.Permissions["mlflow_model_my_model"].AccessControl[0]
assert.Equal(t, "jane@doe.com", p.UserName)
assert.Equal(t, "CAN_READ", p.PermissionLevel)
}
func TestConvertExperiment(t *testing.T) { func TestConvertExperiment(t *testing.T) {
var src = resources.MlflowExperiment{ var src = resources.MlflowExperiment{
Experiment: &mlflow.Experiment{ Experiment: &mlflow.Experiment{
@ -137,3 +218,31 @@ func TestConvertExperiment(t *testing.T) {
assert.Equal(t, "name", out.Resource.MlflowExperiment["my_experiment"].Name) assert.Equal(t, "name", out.Resource.MlflowExperiment["my_experiment"].Name)
assert.Nil(t, out.Data) assert.Nil(t, out.Data)
} }
func TestConvertExperimentPermissions(t *testing.T) {
var src = resources.MlflowExperiment{
Permissions: []resources.Permission{
{
Level: "CAN_READ",
UserName: "jane@doe.com",
},
},
}
var config = config.Root{
Resources: config.Resources{
Experiments: map[string]*resources.MlflowExperiment{
"my_experiment": &src,
},
},
}
out := BundleToTerraform(&config)
assert.NotEmpty(t, out.Resource.Permissions["mlflow_experiment_my_experiment"].ExperimentId)
assert.Len(t, out.Resource.Permissions["mlflow_experiment_my_experiment"].AccessControl, 1)
p := out.Resource.Permissions["mlflow_experiment_my_experiment"].AccessControl[0]
assert.Equal(t, "jane@doe.com", p.UserName)
assert.Equal(t, "CAN_READ", p.PermissionLevel)
}