From 567678a37f23a47ab563450d0394e7210ae44333 Mon Sep 17 00:00:00 2001 From: Tanchwa Date: Mon, 31 Mar 2025 17:48:35 -0400 Subject: [PATCH 1/9] added skelleton for databrikcs permission resource --- .../pluginfw/pluginfw_rollout_utils.go | 3 + .../permission/resource_permission.go | 144 ++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 internal/providers/pluginfw/products/permission/resource_permission.go diff --git a/internal/providers/pluginfw/pluginfw_rollout_utils.go b/internal/providers/pluginfw/pluginfw_rollout_utils.go index 32e40996de..2c8c409392 100644 --- a/internal/providers/pluginfw/pluginfw_rollout_utils.go +++ b/internal/providers/pluginfw/pluginfw_rollout_utils.go @@ -24,6 +24,7 @@ import ( "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/products/serving" "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/products/sharing" "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/products/volume" + "github.com/databricks/terraform-provider-databricks/permission" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/resource" ) @@ -46,6 +47,7 @@ var migratedDataSources = []func() datasource.DataSource{ var pluginFwOnlyResources = append( []func() resource.Resource{ app.ResourceApp, + permission.ResourcePermission, sharing.ResourceShare, }, autoGeneratedResources..., @@ -60,6 +62,7 @@ var pluginFwOnlyDataSources = append( catalog.DataSourceFunctions, dashboards.DataSourceDashboards, notificationdestinations.DataSourceNotificationDestinations, + permission.DataSourcePermission, registered_model.DataSourceRegisteredModel, registered_model.DataSourceRegisteredModelVersions, serving.DataSourceServingEndpoints, diff --git a/internal/providers/pluginfw/products/permission/resource_permission.go b/internal/providers/pluginfw/products/permission/resource_permission.go new file mode 100644 index 0000000000..968a171b7d --- /dev/null +++ b/internal/providers/pluginfw/products/permission/resource_permission.go @@ -0,0 +1,144 @@ +package permission + +import ( + "context" + "errors" + "fmt" + "path" + "strings" + + "github.com/databricks/databricks-sdk-go/apierr" + "github.com/databricks/databricks-sdk-go/service/iam" + "github.com/databricks/terraform-provider-databricks/common" + pluginfwcommon "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/common" + "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/tfschema" + "github.com/databricks/terraform-provider-databricks/permissions/entity" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +const resourceName = "permission" + +var ( + _ resource.Resource = &permissionResource{} + _ resource.ResourceWithConfigure = &permissionResource{} +) + +func NewPermissionResource() resource.Resource { + return &permissionResource{} +} + +type permissionResource struct { + client *common.DatabricksClient + context context.Context +} + +func (r *permissionResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*common.DatabricksClient) + + if !ok { + resp.Diagnostics.AddError( + //TODO ADD ERROR MESSAGE IN LINE WITH PROVIDER + "Unable to configure the Databricks client", + fmt.Sprintf("Expected *common.DatabricksClient, got %T", req.ProviderData), + ) + + return + } + + r.client = client +} + +func (r *permissionResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = pluginfwcommon.GetDatabrikcsProductionName(resourceName) +} + + +type permissionResourceModel struct { + ObjectID types.String `tfsdk:"object_id"` + ObjectType types.String `tfsdk:"object_type"` + AccessControlList []accessControlListModel `tfsdk:"access_control_list"` + LastUpdated types.String `tfsdk:"last_updated"` +} + +type accessControlListModel struct { + ServicePrincipalName types.String `tfsdk:"service_principal_name"` + GroupName types.String `tfsdk:"group_name"` + UserName types.String `tfsdk:"user_name"` + PermissionLevel types.String `tfsdk:"permission_level"` +} + + +//TODO set some attributes as optional, required +func (r *permissionResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + attrs, blocks := tfschema.ResourceStructToSchemaMap(ctx, permissionResourceModel{}, nil) + resp.Schema = schema.Schema{ + Attributes: attrs, + Blocks: blocks, + } +} + + +func (r *permissionResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + ctx := pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) + w, diags := r.client.WorkspaceClient() + resp.Diagnostics.Append(diags...) +} + +func (r *permissionResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + ctx := pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) +} + +func (r *permissionResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + ctx := pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) +} + +func (r *permissionResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + ctx := pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) +} + + +// safePutWithOwner is a workaround for the limitation where warehouse without owners cannot have IS_OWNER set +func (r *permissionResource) safePutWithOwner(ctx context.Context, objectID string, objectACL []iam.AccessControlRequest, mapping resourcePermissions, ownerOpt string) error { + w, err := r.client.WorkspaceClient() + if err != nil { + return err + } + idParts := strings.Split(objectID, "/") + id := idParts[len(idParts)-1] + withOwner := mapping.addOwnerPermissionIfNeeded(objectACL, ownerOpt) + _, err = w.Permissions.Set(ctx, iam.PermissionsRequest{ + RequestObjectId: id, + RequestObjectType: mapping.requestObjectType, + AccessControlList: withOwner, + }) + if err != nil { + if strings.Contains(err.Error(), "with no existing owner must provide a new owner") { + _, err = w.Permissions.Set(ctx, iam.PermissionsRequest{ + RequestObjectId: id, + RequestObjectType: mapping.requestObjectType, + AccessControlList: objectACL, + }) + } + return err + } + return nil +} + +func (r *permissionResource) getCurrentUser(ctx context.Context) (string, error) { + w, err := r.client.WorkspaceClient() + if err != nil { + return "", err + } + me, err := w.CurrentUser.Me(ctx) + if err != nil { + return "", err + } + return me.UserName, nil +} From 6cf9bab98c90f61c8e2c64f05974c74ad98b5874 Mon Sep 17 00:00:00 2001 From: Tanchwa Date: Tue, 1 Apr 2025 10:50:12 -0400 Subject: [PATCH 2/9] started outline for create method, set option to configure workspace or account clients in Configure method --- .../permission/resource_permission.go | 84 ++++++++++++++----- 1 file changed, 61 insertions(+), 23 deletions(-) diff --git a/internal/providers/pluginfw/products/permission/resource_permission.go b/internal/providers/pluginfw/products/permission/resource_permission.go index 968a171b7d..3cc88c93cb 100644 --- a/internal/providers/pluginfw/products/permission/resource_permission.go +++ b/internal/providers/pluginfw/products/permission/resource_permission.go @@ -10,13 +10,15 @@ import ( "github.com/databricks/databricks-sdk-go/apierr" "github.com/databricks/databricks-sdk-go/service/iam" "github.com/databricks/terraform-provider-databricks/common" - pluginfwcommon "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/common" + pluginfwcommon "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/common" + pluginfwcontext "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/context" "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/tfschema" "github.com/databricks/terraform-provider-databricks/permissions/entity" + "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" ) const resourceName = "permission" @@ -40,34 +42,49 @@ func (r *permissionResource) Configure(ctx context.Context, req resource.Configu return } - client, ok := req.ProviderData.(*common.DatabricksClient) - - if !ok { - resp.Diagnostics.AddError( - //TODO ADD ERROR MESSAGE IN LINE WITH PROVIDER - "Unable to configure the Databricks client", - fmt.Sprintf("Expected *common.DatabricksClient, got %T", req.ProviderData), - ) - - return + var provider + //TODO CHECK IF ACCOUNTID ATTRIBUTE IS SET IN PROVIDER CONFIG STRUCT, + // SHOULD BE ABLE TO PULL IF IT'S SET OR NOT FROM THE GOLANG STRUCT ITSELF + if != "" { + accountClient, err := req.ProviderData.(*common.DatabricksClient).AccountClient() + if err != nil { + resp.Diagnostics.AddError( + //TODO ADD ERROR MESSAGE IN LINE WITH PROVIDER + "Unable to configure the Databricks client", + fmt.Sprintf("Expected *common.DatabricksClient, got %T", req.ProviderData), + ) + r.client = accountClient + } + + } else { + workspaceClient, err := req.ProviderData.(*common.DatabricksClient).WorkspaceClient() + if err != nil { + resp.Diagnostics.AddError( + //TODO ADD ERROR MESSAGE IN LINE WITH Provider + "Unable to configure the Databricks client", + fmt.Sprintf("Expected *common.DatabricksClient, got %T", req.ProviderData), + ) + + r.client = workspaceClient + } } - - r.client = client } func (r *permissionResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = pluginfwcommon.GetDatabrikcsProductionName(resourceName) + resp.TypeName = pluginfwcommon.GetDatabricksProductionName(resourceName) } type permissionResourceModel struct { ObjectID types.String `tfsdk:"object_id"` ObjectType types.String `tfsdk:"object_type"` - AccessControlList []accessControlListModel `tfsdk:"access_control_list"` + AccessControlList []permissionAccessControlListModel `tfsdk:"access_control_list"` LastUpdated types.String `tfsdk:"last_updated"` } -type accessControlListModel struct { +//accessControlListModel is the same as iam.AccessControlRequest +// was originally just called this way in entity.go +type permissionAccessControlListModel struct { ServicePrincipalName types.String `tfsdk:"service_principal_name"` GroupName types.String `tfsdk:"group_name"` UserName types.String `tfsdk:"user_name"` @@ -76,6 +93,7 @@ type accessControlListModel struct { //TODO set some attributes as optional, required +// see /databricks/permissions/resource_permissions.go line 160 func (r *permissionResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { attrs, blocks := tfschema.ResourceStructToSchemaMap(ctx, permissionResourceModel{}, nil) resp.Schema = schema.Schema{ @@ -86,21 +104,41 @@ func (r *permissionResource) Schema(ctx context.Context, req resource.SchemaRequ func (r *permissionResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - ctx := pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) - w, diags := r.client.WorkspaceClient() + ctx = pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) + + var plan permissionResourceModel + diags := req.Plan.Get(ctx, &plan) resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // generate API request from plan + var acls []iam.AccessControlRequest + for _, acl := range plan.AccessControlList { + } + + // create the permission + //TODO: FIGURE OUT PROPER API CALL METHOD FOR THIS ACTION + acl, err := r.client.AccessControl.Update(, iam.UpdateRuleSetRequest{ + Name: plan.ObjectID, + ObjectType: plan.ObjectType, + AccessControlList: acls, + }) + + } func (r *permissionResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - ctx := pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) + ctx = pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) } func (r *permissionResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - ctx := pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) + ctx = pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) } func (r *permissionResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - ctx := pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) + ctx = pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) } From 3feb1202c7b349cee23b1b09e396a6434ed97c4d Mon Sep 17 00:00:00 2001 From: Tanchwa Date: Tue, 1 Apr 2025 18:00:57 -0400 Subject: [PATCH 3/9] updated permission and acl request to proper struct names and fields --- .../products/permission/resource_permission.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/internal/providers/pluginfw/products/permission/resource_permission.go b/internal/providers/pluginfw/products/permission/resource_permission.go index 3cc88c93cb..07f86570e8 100644 --- a/internal/providers/pluginfw/products/permission/resource_permission.go +++ b/internal/providers/pluginfw/products/permission/resource_permission.go @@ -116,17 +116,21 @@ func (r *permissionResource) Create(ctx context.Context, req resource.CreateRequ // generate API request from plan var acls []iam.AccessControlRequest for _, acl := range plan.AccessControlList { + acls = append(acls, iam.AccessControlRequest{ + ServicePrincipalName: acl.ServicePrincipalName, + GroupName: acl.GroupName, + UserName: acl.UserName, + PermissionLevel: acl.PermissionLevel, + }) } // create the permission - //TODO: FIGURE OUT PROPER API CALL METHOD FOR THIS ACTION - acl, err := r.client.AccessControl.Update(, iam.UpdateRuleSetRequest{ - Name: plan.ObjectID, - ObjectType: plan.ObjectType, + acl, err := r.client.Update.(plan.ObjectID, iam.PermissionsRequest{ + RequestObjectType: plan.ObjectID, + RequestObjectType: plan.ObjectType, AccessControlList: acls, }) - } func (r *permissionResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { From 4ac9ebe786d3842be433bcb165c5b2e9d308b961 Mon Sep 17 00:00:00 2001 From: Tanchwa Date: Wed, 2 Apr 2025 11:14:05 -0400 Subject: [PATCH 4/9] fixed import path for pluginfw migrate util file, added read and create methods --- go.mod | 19 ++--- go.sum | 20 ----- .../pluginfw/pluginfw_rollout_utils.go | 6 +- .../permission/resource_permission.go | 85 ++++++++++++------- 4 files changed, 62 insertions(+), 68 deletions(-) diff --git a/go.mod b/go.mod index f6907e50f2..ac33021465 100644 --- a/go.mod +++ b/go.mod @@ -25,17 +25,13 @@ require ( cloud.google.com/go/auth v0.4.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect cloud.google.com/go/compute/metadata v0.5.2 // indirect - github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect github.com/ProtonMail/go-crypto v1.1.3 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect - github.com/bitfield/gotestdox v0.2.2 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dnephin/pflag v1.0.7 // indirect github.com/fatih/color v1.17.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -43,7 +39,6 @@ require ( github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/s2a-go v0.1.7 // indirect - github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -81,13 +76,11 @@ require ( go.opentelemetry.io/otel/metric v1.31.0 // indirect go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/crypto v0.36.0 // indirect - golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect golang.org/x/mod v0.24.0 // indirect golang.org/x/net v0.38.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.12.0 // indirect golang.org/x/sys v0.31.0 // indirect - golang.org/x/term v0.30.0 // indirect golang.org/x/text v0.23.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.31.0 // indirect @@ -98,12 +91,10 @@ require ( google.golang.org/protobuf v1.36.3 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gotest.tools/gotestsum v1.12.1 // indirect - honnef.co/go/tools v0.6.0 // indirect ) -tool ( - golang.org/x/tools/cmd/goimports - gotest.tools/gotestsum - honnef.co/go/tools/cmd/staticcheck -) +//tool ( +//golang.org/x/tools/cmd/goimports +//gotest.tools/gotestsum +//honnef.co/go/tools/cmd/staticcheck +//) diff --git a/go.sum b/go.sum index 0274499e98..02e8f1622a 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,6 @@ cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/yb dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= -github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= @@ -19,8 +17,6 @@ github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= -github.com/bitfield/gotestdox v0.2.2 h1:x6RcPAbBbErKLnapz1QeAlf3ospg8efBsedU93CDsnE= -github.com/bitfield/gotestdox v0.2.2/go.mod h1:D+gwtS0urjBrzguAkTM2wodsTQYFHdpx8eqRJ3N+9pY= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -35,8 +31,6 @@ github.com/databricks/databricks-sdk-go v0.63.0/go.mod h1:xBtjeP9nq+6MgTewZW1Ecb github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dnephin/pflag v1.0.7 h1:oxONGlWxhmUct0YzKTgrpQv9AUA1wtPBn7zuSjJqptk= -github.com/dnephin/pflag v1.0.7/go.mod h1:uxE91IoWURlOiTUIA8Mq5ZZkAv3dPUfZNaT80Zm7OQE= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -48,8 +42,6 @@ github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= @@ -98,8 +90,6 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -201,8 +191,6 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjL github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= @@ -256,8 +244,6 @@ golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZv golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= -golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ= -golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -366,11 +352,5 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/gotestsum v1.12.1 h1:dvcxFBTFR1QsQmrCQa4k/vDXow9altdYz4CjdW+XeBE= -gotest.tools/gotestsum v1.12.1/go.mod h1:mwDmLbx9DIvr09dnAoGgQPLaSXszNpXpWo2bsQge5BE= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.6.0 h1:TAODvD3knlq75WCp2nyGJtT4LeRV/o7NN9nYPeVJXf8= -honnef.co/go/tools v0.6.0/go.mod h1:3puzxxljPCe8RGJX7BIy1plGbxEOZni5mR2aXe3/uk4= diff --git a/internal/providers/pluginfw/pluginfw_rollout_utils.go b/internal/providers/pluginfw/pluginfw_rollout_utils.go index 2c8c409392..b15ab2c65f 100644 --- a/internal/providers/pluginfw/pluginfw_rollout_utils.go +++ b/internal/providers/pluginfw/pluginfw_rollout_utils.go @@ -24,7 +24,7 @@ import ( "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/products/serving" "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/products/sharing" "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/products/volume" - "github.com/databricks/terraform-provider-databricks/permission" + "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/products/permission" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/resource" ) @@ -47,7 +47,7 @@ var migratedDataSources = []func() datasource.DataSource{ var pluginFwOnlyResources = append( []func() resource.Resource{ app.ResourceApp, - permission.ResourcePermission, + permission.NewPermissionResource, sharing.ResourceShare, }, autoGeneratedResources..., @@ -62,7 +62,7 @@ var pluginFwOnlyDataSources = append( catalog.DataSourceFunctions, dashboards.DataSourceDashboards, notificationdestinations.DataSourceNotificationDestinations, - permission.DataSourcePermission, + //permission.DataSourcePermission, registered_model.DataSourceRegisteredModel, registered_model.DataSourceRegisteredModelVersions, serving.DataSourceServingEndpoints, diff --git a/internal/providers/pluginfw/products/permission/resource_permission.go b/internal/providers/pluginfw/products/permission/resource_permission.go index 07f86570e8..b97f72a0c8 100644 --- a/internal/providers/pluginfw/products/permission/resource_permission.go +++ b/internal/providers/pluginfw/products/permission/resource_permission.go @@ -42,32 +42,18 @@ func (r *permissionResource) Configure(ctx context.Context, req resource.Configu return } - var provider - //TODO CHECK IF ACCOUNTID ATTRIBUTE IS SET IN PROVIDER CONFIG STRUCT, - // SHOULD BE ABLE TO PULL IF IT'S SET OR NOT FROM THE GOLANG STRUCT ITSELF - if != "" { - accountClient, err := req.ProviderData.(*common.DatabricksClient).AccountClient() - if err != nil { - resp.Diagnostics.AddError( - //TODO ADD ERROR MESSAGE IN LINE WITH PROVIDER - "Unable to configure the Databricks client", - fmt.Sprintf("Expected *common.DatabricksClient, got %T", req.ProviderData), - ) - r.client = accountClient - } - - } else { - workspaceClient, err := req.ProviderData.(*common.DatabricksClient).WorkspaceClient() - if err != nil { - resp.Diagnostics.AddError( - //TODO ADD ERROR MESSAGE IN LINE WITH Provider - "Unable to configure the Databricks client", - fmt.Sprintf("Expected *common.DatabricksClient, got %T", req.ProviderData), - ) - - r.client = workspaceClient - } + + workspaceClient, err := req.ProviderData.(*common.DatabricksClient).WorkspaceClient() + if err != nil { + resp.Diagnostics.AddError( + //TODO ADD ERROR MESSAGE IN LINE WITH Provider + "Unable to configure the Databricks client", + fmt.Sprintf("Expected *common.DatabricksClient, got %T", req.ProviderData), + ) + } + + r.client = workspaceClient } func (r *permissionResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { @@ -118,23 +104,60 @@ func (r *permissionResource) Create(ctx context.Context, req resource.CreateRequ for _, acl := range plan.AccessControlList { acls = append(acls, iam.AccessControlRequest{ ServicePrincipalName: acl.ServicePrincipalName, - GroupName: acl.GroupName, - UserName: acl.UserName, - PermissionLevel: acl.PermissionLevel, + GroupName: types.StringValue(acl.GroupName), + UserName: types.StringValue(acl.UserName), + PermissionLevel: types.StringValue(acl.PermissionLevel), }) } // create the permission - acl, err := r.client.Update.(plan.ObjectID, iam.PermissionsRequest{ - RequestObjectType: plan.ObjectID, - RequestObjectType: plan.ObjectType, + acl, err := r.client.Set.(plan.ObjectID, iam.PermissionsRequest{ + RequestObjectId: types.StringValue(plan.ObjectID), + RequestObjectType: types.StringValue(plan.ObjectType), AccessControlList: acls, }) + plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC850)) + + // Set state to fully populated data + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } } func (r *permissionResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { ctx = pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) + + // Get Current State + var state permissionResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + //Get refreshed acls from API + acl, err := r.client.Get(ctx, state.ObjectID) + if err != nil { + resp.Diagnostics.AddError( + "Failed to get permission", + "Unable to read permission"+state.ObjectID.ValueString()": "+err.Error(), + ) + return + } + + //Overwrite data with refreshed state + state.AccessControlList = []permissionAccessControlListModel{} + + //Set refreshed State + diags = resp.State.Set(ctx, state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + } func (r *permissionResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { From 22cb07f505b6b12939d64161183ca6b0c56c2584 Mon Sep 17 00:00:00 2001 From: Tanchwa Date: Thu, 3 Apr 2025 14:13:04 -0400 Subject: [PATCH 5/9] changed api to generic call without iam package --- .../permission/resource_permission.go | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/internal/providers/pluginfw/products/permission/resource_permission.go b/internal/providers/pluginfw/products/permission/resource_permission.go index b97f72a0c8..15d4aa431e 100644 --- a/internal/providers/pluginfw/products/permission/resource_permission.go +++ b/internal/providers/pluginfw/products/permission/resource_permission.go @@ -21,7 +21,10 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -const resourceName = "permission" +const ( + resourceName = "permission" + apiPath = fmt.Sprintf("/api/2.0/permissions/%s/%s", plan.ObjectType.ValueString(), plan.ObjectID.ValueString()) +) var ( _ resource.Resource = &permissionResource{} @@ -37,6 +40,7 @@ type permissionResource struct { context context.Context } + func (r *permissionResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { if req.ProviderData == nil { return @@ -110,12 +114,20 @@ func (r *permissionResource) Create(ctx context.Context, req resource.CreateRequ }) } + // create the permission - acl, err := r.client.Set.(plan.ObjectID, iam.PermissionsRequest{ - RequestObjectId: types.StringValue(plan.ObjectID), - RequestObjectType: types.StringValue(plan.ObjectType), + err := r.client.Patch(ctx, apiPath, iam.PermissionsRequest{ + RequestObjectId: plan.ObjectID.ValueString(), + RequestObjectType: plan.ObjectType.ValueString(), AccessControlList: acls, }) + if err != nil { + resp.Diagnostics.AddError( + //TODO ADD ERROR MESSAGE IN LINE WITH Provider + "Unable to Create Permission", + fmt.Sprintf("Error: %s", err.Error()), + ) + plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC850)) @@ -138,21 +150,33 @@ func (r *permissionResource) Read(ctx context.Context, req resource.ReadRequest, return } + //Get refreshed acls from API - acl, err := r.client.Get(ctx, state.ObjectID) + var getResponse map[string]interface{} + err := r.client.Get(ctx, apiPath, nil, &getResponse) if err != nil { resp.Diagnostics.AddError( "Failed to get permission", - "Unable to read permission"+state.ObjectID.ValueString()": "+err.Error(), + fmt.Sprintf("Unable to read permission, %s", err.Error()), ) return } //Overwrite data with refreshed state + //TODO: parse getResponse to permissionResourceModel state.AccessControlList = []permissionAccessControlListModel{} + for _, acl := range getResponse.AccessControlList { + state.AccessControlList = append(state.AccessControlList, permissionAccessControlListModel{ + ServicePrincipalName: types.StringValue(acl.ServicePrincipalName), + GroupName: types.StringValue(acl.GroupName), + UserName: types.StringValue(acl.UserName), + PermissionLevel: types.StringValue(acl.PermissionLevel), + }) + } + //Set refreshed State - diags = resp.State.Set(ctx, state) + diags = resp.State.Set(ctx, &state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { return From bc63dd81baaaae849acbd3e5ae77e1446b795ad5 Mon Sep 17 00:00:00 2001 From: Tanchwa Date: Mon, 7 Apr 2025 10:28:13 -0400 Subject: [PATCH 6/9] added schema and confirmed functionality with terraform; still having issues with databricks API calls --- go.mod | 6 +- .../permission/resource_permission.go | 421 +++++++++++------- 2 files changed, 255 insertions(+), 172 deletions(-) diff --git a/go.mod b/go.mod index ac33021465..d071dd790b 100644 --- a/go.mod +++ b/go.mod @@ -94,7 +94,7 @@ require ( ) //tool ( -//golang.org/x/tools/cmd/goimports -//gotest.tools/gotestsum -//honnef.co/go/tools/cmd/staticcheck +// golang.org/x/tools/cmd/goimports +// gotest.tools/gotestsum +// honnef.co/go/tools/cmd/staticcheck //) diff --git a/internal/providers/pluginfw/products/permission/resource_permission.go b/internal/providers/pluginfw/products/permission/resource_permission.go index 15d4aa431e..94e9c742f8 100644 --- a/internal/providers/pluginfw/products/permission/resource_permission.go +++ b/internal/providers/pluginfw/products/permission/resource_permission.go @@ -4,230 +4,313 @@ import ( "context" "errors" "fmt" - "path" + "reflect" "strings" + "time" + "github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go/apierr" "github.com/databricks/databricks-sdk-go/service/iam" "github.com/databricks/terraform-provider-databricks/common" pluginfwcommon "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/common" pluginfwcontext "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/context" "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/tfschema" - "github.com/databricks/terraform-provider-databricks/permissions/entity" - - "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/types" ) const ( - resourceName = "permission" - apiPath = fmt.Sprintf("/api/2.0/permissions/%s/%s", plan.ObjectType.ValueString(), plan.ObjectID.ValueString()) + resourceName = "permission" ) var ( - _ resource.Resource = &permissionResource{} - _ resource.ResourceWithConfigure = &permissionResource{} + apiPath string + //_ resource.Resource = &PermissionResource{} + //_ resource.ResourceWithConfigure = &PermissionResource{} ) func NewPermissionResource() resource.Resource { - return &permissionResource{} + return &PermissionResource{} } -type permissionResource struct { - client *common.DatabricksClient - context context.Context +type PermissionResource struct { + client *common.DatabricksClient + workspaceClient databricks.WorkspaceClient + context context.Context } +func (r *PermissionResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } -func (r *permissionResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { - if req.ProviderData == nil { - return - } - - - workspaceClient, err := req.ProviderData.(*common.DatabricksClient).WorkspaceClient() - if err != nil { - resp.Diagnostics.AddError( - //TODO ADD ERROR MESSAGE IN LINE WITH Provider - "Unable to configure the Databricks client", - fmt.Sprintf("Expected *common.DatabricksClient, got %T", req.ProviderData), - ) - - } - - r.client = workspaceClient + workspaceClient, err := req.ProviderData.(*common.DatabricksClient).WorkspaceClient() + if err != nil { + resp.Diagnostics.AddError( + //TODO ADD ERROR MESSAGE IN LINE WITH Provider + "Unable to configure the Databricks client", + fmt.Sprintf("Expected *common.DatabricksClient, got %T", req.ProviderData), + ) + + } else { + r.workspaceClient = *workspaceClient + } } -func (r *permissionResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = pluginfwcommon.GetDatabricksProductionName(resourceName) +func (r *PermissionResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = pluginfwcommon.GetDatabricksProductionName(resourceName) } - type permissionResourceModel struct { - ObjectID types.String `tfsdk:"object_id"` - ObjectType types.String `tfsdk:"object_type"` - AccessControlList []permissionAccessControlListModel `tfsdk:"access_control_list"` - LastUpdated types.String `tfsdk:"last_updated"` + ObjectID types.String `tfsdk:"object_id"` + ObjectType types.String `tfsdk:"object_type"` + AccessControlList []permissionAccessControlListModel `tfsdk:"access_control"` + LastUpdated types.String `tfsdk:"last_updated"` } -//accessControlListModel is the same as iam.AccessControlRequest +// accessControlListModel is the same as iam.AccessControlRequest // was originally just called this way in entity.go type permissionAccessControlListModel struct { - ServicePrincipalName types.String `tfsdk:"service_principal_name"` - GroupName types.String `tfsdk:"group_name"` - UserName types.String `tfsdk:"user_name"` - PermissionLevel types.String `tfsdk:"permission_level"` + ServicePrincipalId types.String `tfsdk:"service_principal_id"` + GroupName types.String `tfsdk:"group_name"` + UserName types.String `tfsdk:"user_name"` + PermissionLevel types.String `tfsdk:"permission_level"` } +func (permissionResourceModel) ApplySchemaCustomizations(attrs map[string]tfschema.AttributeBuilder) map[string]tfschema.AttributeBuilder { + attrs["object_id"] = attrs["object_id"].SetOptional().SetComputed() + attrs["object_type"] = attrs["object_type"].SetOptional().SetComputed() + attrs["access_control"] = attrs["access_control"].SetRequired().SetComputed() -//TODO set some attributes as optional, required -// see /databricks/permissions/resource_permissions.go line 160 -func (r *permissionResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { - attrs, blocks := tfschema.ResourceStructToSchemaMap(ctx, permissionResourceModel{}, nil) - resp.Schema = schema.Schema{ - Attributes: attrs, - Blocks: blocks, - } + return attrs } -func (r *permissionResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - ctx = pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) - - var plan permissionResourceModel - diags := req.Plan.Get(ctx, &plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - // generate API request from plan - var acls []iam.AccessControlRequest - for _, acl := range plan.AccessControlList { - acls = append(acls, iam.AccessControlRequest{ - ServicePrincipalName: acl.ServicePrincipalName, - GroupName: types.StringValue(acl.GroupName), - UserName: types.StringValue(acl.UserName), - PermissionLevel: types.StringValue(acl.PermissionLevel), - }) - } - - - // create the permission - err := r.client.Patch(ctx, apiPath, iam.PermissionsRequest{ - RequestObjectId: plan.ObjectID.ValueString(), - RequestObjectType: plan.ObjectType.ValueString(), - AccessControlList: acls, - }) - if err != nil { - resp.Diagnostics.AddError( - //TODO ADD ERROR MESSAGE IN LINE WITH Provider - "Unable to Create Permission", - fmt.Sprintf("Error: %s", err.Error()), - ) - - - plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC850)) - - // Set state to fully populated data - diags = resp.State.Set(ctx, plan) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } +func (permissionResourceModel) GetComplexFieldTypes(context.Context) map[string]reflect.Type { + return map[string]reflect.Type{ + "access_control" : reflect.TypeOf(permissionAccessControlListModel{}), + } } -func (r *permissionResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - ctx = pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) - - // Get Current State - var state permissionResourceModel - diags := req.State.Get(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - - //Get refreshed acls from API - var getResponse map[string]interface{} - err := r.client.Get(ctx, apiPath, nil, &getResponse) - if err != nil { - resp.Diagnostics.AddError( - "Failed to get permission", - fmt.Sprintf("Unable to read permission, %s", err.Error()), - ) - return +func (r *PermissionResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "object_id": schema.StringAttribute{ + Computed: true, + Optional: true, + }, + "object_type": schema.StringAttribute{ + Computed: true, + Optional: true, + }, + "last_updated": schema.StringAttribute{ + Computed: true, + }, + }, + Blocks: map[string]schema.Block{ + "access_control": schema.SetNestedBlock{ + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "service_principal_id": schema.StringAttribute{ + Computed: true, + Optional: true, + Description: "The service principal ID of the access control entry.", + }, + "group_name": schema.StringAttribute{ + Computed: true, + Optional: true, + Description: "The group name of the access control entry.", + }, + "user_name": schema.StringAttribute{ + Computed: true, + Optional: true, + Description: "The user name of the access control entry.", + }, + "permission_level": schema.StringAttribute{ + Computed: true, + Optional: true, + Description: "The permission level of the access control entry.", + }, + }, + }, + }, + }, } +} - //Overwrite data with refreshed state - //TODO: parse getResponse to permissionResourceModel - state.AccessControlList = []permissionAccessControlListModel{} - for _, acl := range getResponse.AccessControlList { - state.AccessControlList = append(state.AccessControlList, permissionAccessControlListModel{ - ServicePrincipalName: types.StringValue(acl.ServicePrincipalName), - GroupName: types.StringValue(acl.GroupName), - UserName: types.StringValue(acl.UserName), - PermissionLevel: types.StringValue(acl.PermissionLevel), - }) - } +// TODO set some attributes as optional, required +// see /databricks/permissions/resource_permissions.go line 160 +//func (r *PermissionResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { +// attrs, blocks := tfschema.ResourceStructToSchemaMap(ctx, permissionResourceModel{}, nil) +// resp.Schema = schema.Schema{ +// Attributes: attrs, +// Blocks: blocks, +// } +//} + +func (r *PermissionResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + ctx = pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) + + var plan permissionResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + // generate API request from plan + var acls []iam.AccessControlRequest + for _, acl := range plan.AccessControlList { + acls = append(acls, iam.AccessControlRequest{ + ServicePrincipalName: acl.ServicePrincipalId.String(), + GroupName: acl.GroupName.String(), + UserName: acl.UserName.String(), + PermissionLevel: iam.PermissionLevel(acl.PermissionLevel.String()), + }) + } - //Set refreshed State - diags = resp.State.Set(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } + // create the permission + permission, err := r.workspaceClient.Permissions.Update(ctx, iam.PermissionsRequest{ + RequestObjectId: plan.ObjectID.ValueString(), + RequestObjectType: plan.ObjectType.ValueString(), + AccessControlList: acls, + }) + if err != nil { + resp.Diagnostics.AddError( + //TODO ADD ERROR MESSAGE IN LINE WITH Provider + "Unable to Create Permission", + fmt.Sprintf("Error: %s", err.Error()), + ) + } + + //Map response to to schema and populate Computed attribute values + plan.ObjectID = types.StringValue(permission.ObjectId) + plan.ObjectType = types.StringValue(permission.ObjectType) + for permissionAclIndex, permissionAcl := range permission.AccessControlList { + plan.AccessControlList[permissionAclIndex] = permissionAccessControlListModel{ + ServicePrincipalId: types.StringValue(permissionAcl.ServicePrincipalName), + GroupName: types.StringValue(permissionAcl.GroupName), + UserName: types.StringValue(permissionAcl.UserName), + PermissionLevel: types.StringValue(string(permissionAcl.AllPermissions[permissionAclIndex].PermissionLevel)), + } + } + + + plan.LastUpdated = types.StringValue(time.Now().Format(time.RFC850)) + + // Set state to fully populated data + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } } -func (r *permissionResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - ctx = pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) -} + //apiPath = fmt.Sprintf("/api/2.0/permissions/%s/%s", plan.ObjectType.ValueString(), plan.ObjectID.ValueString()) -func (r *permissionResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - ctx = pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) -} +func (r *PermissionResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + ctx = pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) + // Get Current State + var state permissionResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } -// safePutWithOwner is a workaround for the limitation where warehouse without owners cannot have IS_OWNER set -func (r *permissionResource) safePutWithOwner(ctx context.Context, objectID string, objectACL []iam.AccessControlRequest, mapping resourcePermissions, ownerOpt string) error { - w, err := r.client.WorkspaceClient() - if err != nil { - return err + //Get refreshed acls from API + permissions, err := common.RetryOn504(ctx, func(ctx context.Context) (*iam.ObjectPermissions, error) { + return r.workspaceClient.Permissions.Get(ctx, iam.GetPermissionRequest{ + RequestObjectId: state.ObjectID.ValueString(), + RequestObjectType: state.ObjectType.ValueString(), + }) + }) + + var apiErr *apierr.APIError + // https://github.com/databricks/terraform-provider-databricks/issues/1227 + // platform propagates INVALID_STATE error for auto-purged clusters in + // the permissions api. this adds "a logical fix" also here, not to introduce + // cross-package dependency on "clusters". + if errors.As(err, &apiErr) && strings.Contains(apiErr.Message, "Cannot access cluster") && apiErr.StatusCode == 400 { + apiErr.StatusCode = 404 + apiErr.ErrorCode = "RESOURCE_DOES_NOT_EXIST" + err = apiErr } - idParts := strings.Split(objectID, "/") - id := idParts[len(idParts)-1] - withOwner := mapping.addOwnerPermissionIfNeeded(objectACL, ownerOpt) - _, err = w.Permissions.Set(ctx, iam.PermissionsRequest{ - RequestObjectId: id, - RequestObjectType: mapping.requestObjectType, - AccessControlList: withOwner, - }) if err != nil { - if strings.Contains(err.Error(), "with no existing owner must provide a new owner") { - _, err = w.Permissions.Set(ctx, iam.PermissionsRequest{ - RequestObjectId: id, - RequestObjectType: mapping.requestObjectType, - AccessControlList: objectACL, - }) - } - return err + resp.Diagnostics.AddError( + "Failed to get permission", + fmt.Sprintf("Unable to read permission, %s", err.Error()), + ) + return } - return nil -} -func (r *permissionResource) getCurrentUser(ctx context.Context) (string, error) { - w, err := r.client.WorkspaceClient() - if err != nil { - return "", err - } - me, err := w.CurrentUser.Me(ctx) - if err != nil { - return "", err + + //Overwrite data with refreshed state + //TODO: parse getResponse to permissionResourceModel + state.AccessControlList = []permissionAccessControlListModel{} + for aclIndex, acl := range permissions.AccessControlList { + state.AccessControlList = append(state.AccessControlList, permissionAccessControlListModel{ + ServicePrincipalId: types.StringValue(acl.ServicePrincipalName), + GroupName: types.StringValue(acl.GroupName), + UserName: types.StringValue(acl.UserName), + PermissionLevel: types.StringValue(string(acl.AllPermissions[aclIndex].PermissionLevel)), + }) + } + + //Set refreshed State + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return } - return me.UserName, nil } + +func (r *PermissionResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + ctx = pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) +} + +func (r *PermissionResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + ctx = pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) +} + +// safePutWithOwner is a workaround for the limitation where warehouse without owners cannot have IS_OWNER set +//func (r *PermissionResource) safePutWithOwner(ctx context.Context, objectID string, objectACL []iam.AccessControlRequest, mapping resourcePermissions, ownerOpt string) error { +// w, err := r.client.WorkspaceClient() +// if err != nil { +// return err +// } +// idParts := strings.Split(objectID, "/") +// id := idParts[len(idParts)-1] +// withOwner := mapping.addOwnerPermissionIfNeeded(objectACL, ownerOpt) +// _, err = w.Permissions.Set(ctx, iam.PermissionsRequest{ +// RequestObjectId: id, +// RequestObjectType: mapping.requestObjectType, +// AccessControlList: withOwner, +// }) +// if err != nil { +// if strings.Contains(err.Error(), "with no existing owner must provide a new owner") { +// _, err = w.Permissions.Set(ctx, iam.PermissionsRequest{ +// RequestObjectId: id, +// RequestObjectType: mapping.requestObjectType, +// AccessControlList: objectACL, +// }) +// } +// return err +// } +// return nil +//} + +//func (r *PermissionResource) getCurrentUser(ctx context.Context) (string, error) { +// w, err := r.client.WorkspaceClient() +// if err != nil { +// return "", err +// } +// me, err := w.CurrentUser.Me(ctx) +// if err != nil { +// return "", err +// } +// return me.UserName, nil +//} From 8245c23fce822f77888ab2512fe2a94d9ac26e4d Mon Sep 17 00:00:00 2001 From: Tanchwa Date: Mon, 7 Apr 2025 12:29:28 -0400 Subject: [PATCH 7/9] fixed API call by trimming quotes from input strings --- .../pluginfw/products/permission/resource_permission.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/providers/pluginfw/products/permission/resource_permission.go b/internal/providers/pluginfw/products/permission/resource_permission.go index 94e9c742f8..7e9aa92d26 100644 --- a/internal/providers/pluginfw/products/permission/resource_permission.go +++ b/internal/providers/pluginfw/products/permission/resource_permission.go @@ -163,10 +163,10 @@ func (r *PermissionResource) Create(ctx context.Context, req resource.CreateRequ var acls []iam.AccessControlRequest for _, acl := range plan.AccessControlList { acls = append(acls, iam.AccessControlRequest{ - ServicePrincipalName: acl.ServicePrincipalId.String(), - GroupName: acl.GroupName.String(), - UserName: acl.UserName.String(), - PermissionLevel: iam.PermissionLevel(acl.PermissionLevel.String()), + //ServicePrincipalName: strings.Trim(acl.ServicePrincipalId.String(), "\""), + GroupName: strings.Trim(acl.GroupName.String(), "\""), + //UserName: strings.Trim(acl.UserName.String(), "\""), + PermissionLevel: iam.PermissionLevel(strings.Trim(acl.PermissionLevel.String(), "\"")), }) } From ac8025c3465b7dc159b0a1159c3f9b891cda1bcd Mon Sep 17 00:00:00 2001 From: Tanchwa Date: Wed, 9 Apr 2025 11:29:28 -0400 Subject: [PATCH 8/9] resource now only supports single access control block --- .../permission/resource_permission.go | 163 ++++++++++-------- 1 file changed, 92 insertions(+), 71 deletions(-) diff --git a/internal/providers/pluginfw/products/permission/resource_permission.go b/internal/providers/pluginfw/products/permission/resource_permission.go index 7e9aa92d26..99936b1484 100644 --- a/internal/providers/pluginfw/products/permission/resource_permission.go +++ b/internal/providers/pluginfw/products/permission/resource_permission.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "reflect" "strings" "time" @@ -14,10 +13,11 @@ import ( "github.com/databricks/terraform-provider-databricks/common" pluginfwcommon "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/common" pluginfwcontext "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/context" - "github.com/databricks/terraform-provider-databricks/internal/providers/pluginfw/tfschema" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/path" ) const ( @@ -25,9 +25,9 @@ const ( ) var ( - apiPath string - //_ resource.Resource = &PermissionResource{} - //_ resource.ResourceWithConfigure = &PermissionResource{} + _ resource.Resource = &PermissionResource{} + _ resource.ResourceWithConfigure = &PermissionResource{} + _ validator.Object = &accessControlListValidator{} ) func NewPermissionResource() resource.Resource { @@ -65,75 +65,105 @@ func (r *PermissionResource) Metadata(ctx context.Context, req resource.Metadata type permissionResourceModel struct { ObjectID types.String `tfsdk:"object_id"` ObjectType types.String `tfsdk:"object_type"` - AccessControlList []permissionAccessControlListModel `tfsdk:"access_control"` + AccessControlList permissionAccessControlModel `tfsdk:"access_control"` LastUpdated types.String `tfsdk:"last_updated"` } // accessControlListModel is the same as iam.AccessControlRequest // was originally just called this way in entity.go -type permissionAccessControlListModel struct { +type permissionAccessControlModel struct { ServicePrincipalId types.String `tfsdk:"service_principal_id"` GroupName types.String `tfsdk:"group_name"` UserName types.String `tfsdk:"user_name"` PermissionLevel types.String `tfsdk:"permission_level"` } -func (permissionResourceModel) ApplySchemaCustomizations(attrs map[string]tfschema.AttributeBuilder) map[string]tfschema.AttributeBuilder { - attrs["object_id"] = attrs["object_id"].SetOptional().SetComputed() - attrs["object_type"] = attrs["object_type"].SetOptional().SetComputed() - attrs["access_control"] = attrs["access_control"].SetRequired().SetComputed() +type accessControlListValidator struct {} - return attrs +func (v accessControlListValidator) Description(ctx context.Context) string { + return "Only one of user_name, group_name, or service_principal_id may be set" } +func (v accessControlListValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} -func (permissionResourceModel) GetComplexFieldTypes(context.Context) map[string]reflect.Type { - return map[string]reflect.Type{ - "access_control" : reflect.TypeOf(permissionAccessControlListModel{}), - } + +func (v accessControlListValidator) ValidateObject(ctx context.Context, req validator.ObjectRequest, resp *validator.ObjectResponse) { + var userName, groupName, spID types.String + + // You must check for each field using its path + if diags := req.Config.GetAttribute(ctx, path.Root("access_control").AtName("user_name"), &userName); diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + if diags := req.Config.GetAttribute(ctx, path.Root("access_control").AtName("group_name"), &groupName); diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + if diags := req.Config.GetAttribute(ctx, path.Root("access_control").AtName("service_principal_id"), &spID); diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + + // Count how many are set + count := 0 + if !userName.IsNull() && userName.ValueString() != "" { + count++ + } + if !groupName.IsNull() && groupName.ValueString() != "" { + count++ + } + if !spID.IsNull() && spID.ValueString() != "" { + count++ + } + + if count != 1 { + resp.Diagnostics.AddError( + "Invalid access control configuration", + "Exactly one of `user_name`, `group_name`, or `service_principal_id` must be set.", + ) + } } + + func (r *PermissionResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Attributes: map[string]schema.Attribute{ "object_id": schema.StringAttribute{ - Computed: true, - Optional: true, + Required: true, }, "object_type": schema.StringAttribute{ - Computed: true, - Optional: true, + Required: true, }, "last_updated": schema.StringAttribute{ Computed: true, }, }, Blocks: map[string]schema.Block{ - "access_control": schema.SetNestedBlock{ - NestedObject: schema.NestedBlockObject{ - Attributes: map[string]schema.Attribute{ - "service_principal_id": schema.StringAttribute{ - Computed: true, - Optional: true, - Description: "The service principal ID of the access control entry.", - }, - "group_name": schema.StringAttribute{ - Computed: true, - Optional: true, - Description: "The group name of the access control entry.", - }, - "user_name": schema.StringAttribute{ - Computed: true, - Optional: true, - Description: "The user name of the access control entry.", - }, - "permission_level": schema.StringAttribute{ - Computed: true, - Optional: true, - Description: "The permission level of the access control entry.", - }, + "access_control": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "service_principal_id": schema.StringAttribute{ + Optional: true, + Description: "The service principal ID of the access control entry.", + }, + "group_name": schema.StringAttribute{ + Optional: true, + Description: "The group name of the access control entry.", + }, + "user_name": schema.StringAttribute{ + Optional: true, + Description: "The user name of the access control entry.", + }, + "permission_level": schema.StringAttribute{ + Optional: true, + Description: "The permission level of the access control entry.", + }, + }, + Validators: []validator.Object{ + accessControlListValidator{}, }, - }, }, }, } @@ -159,23 +189,20 @@ func (r *PermissionResource) Create(ctx context.Context, req resource.CreateRequ return } + // generate API request from plan - var acls []iam.AccessControlRequest - for _, acl := range plan.AccessControlList { - acls = append(acls, iam.AccessControlRequest{ - //ServicePrincipalName: strings.Trim(acl.ServicePrincipalId.String(), "\""), - GroupName: strings.Trim(acl.GroupName.String(), "\""), - //UserName: strings.Trim(acl.UserName.String(), "\""), - PermissionLevel: iam.PermissionLevel(strings.Trim(acl.PermissionLevel.String(), "\"")), - }) - } + var acl iam.AccessControlRequest + acl.ServicePrincipalName = strings.Trim(plan.AccessControlList.ServicePrincipalId.String(), "\"") + acl.GroupName = strings.Trim(plan.AccessControlList.GroupName.String(), "\"") + acl.UserName = strings.Trim(plan.AccessControlList.UserName.String(), "\"") + acl.PermissionLevel = iam.PermissionLevel(strings.Trim(plan.AccessControlList.PermissionLevel.String(), "\"")) // create the permission permission, err := r.workspaceClient.Permissions.Update(ctx, iam.PermissionsRequest{ RequestObjectId: plan.ObjectID.ValueString(), RequestObjectType: plan.ObjectType.ValueString(), - AccessControlList: acls, + AccessControlList: []iam.AccessControlRequest{acl}, }) if err != nil { resp.Diagnostics.AddError( @@ -188,13 +215,11 @@ func (r *PermissionResource) Create(ctx context.Context, req resource.CreateRequ //Map response to to schema and populate Computed attribute values plan.ObjectID = types.StringValue(permission.ObjectId) plan.ObjectType = types.StringValue(permission.ObjectType) - for permissionAclIndex, permissionAcl := range permission.AccessControlList { - plan.AccessControlList[permissionAclIndex] = permissionAccessControlListModel{ - ServicePrincipalId: types.StringValue(permissionAcl.ServicePrincipalName), - GroupName: types.StringValue(permissionAcl.GroupName), - UserName: types.StringValue(permissionAcl.UserName), - PermissionLevel: types.StringValue(string(permissionAcl.AllPermissions[permissionAclIndex].PermissionLevel)), - } + plan.AccessControlList = permissionAccessControlModel{ + ServicePrincipalId: types.StringValue(permission.AccessControlList[0].ServicePrincipalName), + GroupName: types.StringValue(permission.AccessControlList[0].GroupName), + UserName: types.StringValue(permission.AccessControlList[0].UserName), + PermissionLevel: types.StringValue(string(permission.AccessControlList[0].AllPermissions[0].PermissionLevel)), } @@ -208,7 +233,6 @@ func (r *PermissionResource) Create(ctx context.Context, req resource.CreateRequ } } - //apiPath = fmt.Sprintf("/api/2.0/permissions/%s/%s", plan.ObjectType.ValueString(), plan.ObjectID.ValueString()) func (r *PermissionResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { ctx = pluginfwcontext.SetUserAgentInResourceContext(ctx, resourceName) @@ -250,15 +274,12 @@ func (r *PermissionResource) Read(ctx context.Context, req resource.ReadRequest, //Overwrite data with refreshed state //TODO: parse getResponse to permissionResourceModel - state.AccessControlList = []permissionAccessControlListModel{} - for aclIndex, acl := range permissions.AccessControlList { - state.AccessControlList = append(state.AccessControlList, permissionAccessControlListModel{ - ServicePrincipalId: types.StringValue(acl.ServicePrincipalName), - GroupName: types.StringValue(acl.GroupName), - UserName: types.StringValue(acl.UserName), - PermissionLevel: types.StringValue(string(acl.AllPermissions[aclIndex].PermissionLevel)), - }) - } + state.AccessControlList.UserName = types.StringValue(permissions.AccessControlList[0].UserName) + state.AccessControlList.GroupName = types.StringValue(permissions.AccessControlList[0].GroupName) + state.AccessControlList.ServicePrincipalId = types.StringValue(permissions.AccessControlList[0].ServicePrincipalName) + state.AccessControlList.PermissionLevel = types.StringValue(string(permissions.AccessControlList[0].AllPermissions[0].PermissionLevel)) + state.ObjectID = types.StringValue(permissions.ObjectId) + state.ObjectType = types.StringValue(permissions.ObjectType) //Set refreshed State diags = resp.State.Set(ctx, &state) From 8c77103e4f0e3fa83d702497250567f32e936547 Mon Sep 17 00:00:00 2001 From: Tanchwa Date: Wed, 9 Apr 2025 12:16:33 -0400 Subject: [PATCH 9/9] fixed null string value issues; was accidentally using (types.String).String instead of (types.String).ValueString --- .../products/permission/resource_permission.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/providers/pluginfw/products/permission/resource_permission.go b/internal/providers/pluginfw/products/permission/resource_permission.go index 99936b1484..ed230cc308 100644 --- a/internal/providers/pluginfw/products/permission/resource_permission.go +++ b/internal/providers/pluginfw/products/permission/resource_permission.go @@ -92,7 +92,6 @@ func (v accessControlListValidator) MarkdownDescription(ctx context.Context) str func (v accessControlListValidator) ValidateObject(ctx context.Context, req validator.ObjectRequest, resp *validator.ObjectResponse) { var userName, groupName, spID types.String - // You must check for each field using its path if diags := req.Config.GetAttribute(ctx, path.Root("access_control").AtName("user_name"), &userName); diags.HasError() { resp.Diagnostics.Append(diags...) return @@ -188,14 +187,15 @@ func (r *PermissionResource) Create(ctx context.Context, req resource.CreateRequ if resp.Diagnostics.HasError() { return } - + + // generate API request from plan var acl iam.AccessControlRequest - acl.ServicePrincipalName = strings.Trim(plan.AccessControlList.ServicePrincipalId.String(), "\"") - acl.GroupName = strings.Trim(plan.AccessControlList.GroupName.String(), "\"") - acl.UserName = strings.Trim(plan.AccessControlList.UserName.String(), "\"") - acl.PermissionLevel = iam.PermissionLevel(strings.Trim(plan.AccessControlList.PermissionLevel.String(), "\"")) + acl.ServicePrincipalName = plan.AccessControlList.ServicePrincipalId.ValueString() + acl.UserName = plan.AccessControlList.UserName.ValueString() + acl.GroupName = plan.AccessControlList.GroupName.ValueString() + acl.PermissionLevel = iam.PermissionLevel(plan.AccessControlList.PermissionLevel.ValueString()) // create the permission