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"
type Job struct {
ID string `json:"id,omitempty"`
ID string `json:"id,omitempty"`
Permissions []Permission `json:"permissions,omitempty"`
*jobs.JobSettings
}

View File

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

View File

@ -3,5 +3,7 @@ package resources
import "github.com/databricks/databricks-sdk-go/service/mlflow"
type MlflowModel struct {
Permissions []Permission `json:"permissions,omitempty"`
*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"
type Pipeline struct {
ID string `json:"id,omitempty"`
ID string `json:"id,omitempty"`
Permissions []Permission `json:"permissions,omitempty"`
*pipelines.PipelineSpec
}

View File

@ -5,6 +5,7 @@ import (
"fmt"
"github.com/databricks/bricks/bundle/config"
"github.com/databricks/bricks/bundle/config/resources"
"github.com/databricks/bricks/bundle/internal/tf/schema"
tfjson "github.com/hashicorp/terraform-json"
)
@ -14,6 +15,35 @@ func conv(from any, to any) {
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
// to the equivalent Terraform JSON representation.
//
@ -29,68 +59,96 @@ func BundleToTerraform(config *config.Root) *schema.Root {
var dst schema.ResourceJob
conv(src, &dst)
for _, v := range src.Tasks {
var t schema.ResourceJobTask
conv(v, &t)
if src.JobSettings != nil {
for _, v := range src.Tasks {
var t schema.ResourceJobTask
conv(v, &t)
for _, v_ := range v.Libraries {
var l schema.ResourceJobTaskLibrary
conv(v_, &l)
t.Library = append(t.Library, l)
for _, v_ := range v.Libraries {
var l schema.ResourceJobTaskLibrary
conv(v_, &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 {
var t schema.ResourceJobJobCluster
conv(v, &t)
dst.JobCluster = append(dst.JobCluster, t)
}
// Unblock downstream work. To be addressed more generally later.
if git := src.GitSource; git != nil {
dst.GitSource = &schema.ResourceJobGitSource{
Url: git.GitUrl,
Branch: git.GitBranch,
Commit: git.GitCommit,
Provider: string(git.GitProvider),
Tag: git.GitTag,
// Unblock downstream work. To be addressed more generally later.
if git := src.GitSource; git != nil {
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
// 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 {
var dst schema.ResourcePipeline
conv(src, &dst)
for _, v := range src.Libraries {
var l schema.ResourcePipelineLibrary
conv(v, &l)
dst.Library = append(dst.Library, l)
}
if src.PipelineSpec != nil {
for _, v := range src.Libraries {
var l schema.ResourcePipelineLibrary
conv(v, &l)
dst.Library = append(dst.Library, l)
}
for _, v := range src.Clusters {
var l schema.ResourcePipelineCluster
conv(v, &l)
dst.Cluster = append(dst.Cluster, l)
for _, v := range src.Clusters {
var l schema.ResourcePipelineCluster
conv(v, &l)
dst.Cluster = append(dst.Cluster, l)
}
}
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 {
var dst schema.ResourceMlflowModel
conv(src, &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 {
var dst schema.ResourceMlflowExperiment
conv(src, &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

View File

@ -47,6 +47,33 @@ func TestConvertJob(t *testing.T) {
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) {
var src = resources.Job{
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)
}
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) {
var src = resources.MlflowModel{
RegisteredModel: &mlflow.RegisteredModel{
@ -118,6 +172,33 @@ func TestConvertModel(t *testing.T) {
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) {
var src = resources.MlflowExperiment{
Experiment: &mlflow.Experiment{
@ -137,3 +218,31 @@ func TestConvertExperiment(t *testing.T) {
assert.Equal(t, "name", out.Resource.MlflowExperiment["my_experiment"].Name)
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)
}