Skip to content
34 changes: 17 additions & 17 deletions scim/data_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,35 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

type groupData struct {
entitlements
DisplayName string `json:"display_name"`
Recursive bool `json:"recursive,omitempty"`
Members []string `json:"members,omitempty" tf:"slice_set,computed"`
Users []string `json:"users,omitempty" tf:"slice_set,computed"`
ServicePrincipals []string `json:"service_principals,omitempty" tf:"slice_set,computed"`
ChildGroups []string `json:"child_groups,omitempty" tf:"slice_set,computed"`
Groups []string `json:"groups,omitempty" tf:"slice_set,computed"`
InstanceProfiles []string `json:"instance_profiles,omitempty" tf:"slice_set,computed"`
ExternalID string `json:"external_id,omitempty" tf:"computed"`
AclPrincipalID string `json:"acl_principal_id,omitempty" tf:"computed"`
}

// DataSourceGroup returns information about group specified by display name
func DataSourceGroup() common.Resource {
type entity struct {
DisplayName string `json:"display_name"`
Recursive bool `json:"recursive,omitempty"`
Members []string `json:"members,omitempty" tf:"slice_set,computed"`
Users []string `json:"users,omitempty" tf:"slice_set,computed"`
ServicePrincipals []string `json:"service_principals,omitempty" tf:"slice_set,computed"`
ChildGroups []string `json:"child_groups,omitempty" tf:"slice_set,computed"`
Groups []string `json:"groups,omitempty" tf:"slice_set,computed"`
InstanceProfiles []string `json:"instance_profiles,omitempty" tf:"slice_set,computed"`
ExternalID string `json:"external_id,omitempty" tf:"computed"`
AclPrincipalID string `json:"acl_principal_id,omitempty" tf:"computed"`
}

s := common.StructToSchema(entity{}, func(
s := common.StructToSchema(groupData{}, func(
s map[string]*schema.Schema) map[string]*schema.Schema {
// nolint once SDKv2 has Diagnostics-returning validators, change
s["display_name"].ValidateFunc = validation.StringIsNotEmpty
s["recursive"].Default = true
s["members"].Deprecated = "Please use `users`, `service_principals`, and `child_groups` instead"
addEntitlementsToSchema(s)
return s
})

return common.Resource{
Schema: s,
Read: func(ctx context.Context, d *schema.ResourceData, m *common.DatabricksClient) error {
var this entity
var this groupData
common.DataToStructPointer(d, s, &this)
groupsAPI := NewGroupsAPI(ctx, m)
groupAttributes := "members,roles,entitlements,externalId"
Expand Down Expand Up @@ -67,7 +67,7 @@ func DataSourceGroup() common.Resource {
for _, x := range current.Roles {
this.InstanceProfiles = append(this.InstanceProfiles, x.Value)
}
current.Entitlements.readIntoData(d)
this.entitlements = newEntitlements(ctx, current.Entitlements)
for _, x := range current.Groups {
this.Groups = append(this.Groups, x.Value)
if this.Recursive {
Expand Down
59 changes: 59 additions & 0 deletions scim/entitlements.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package scim

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-log/tflog"
)

type entitlements struct {
AllowClusterCreate bool `json:"allow_cluster_create,omitempty"`
AllowInstancePoolCreate bool `json:"allow_instance_pool_create,omitempty"`
DatabricksSQLAccess bool `json:"databricks_sql_access,omitempty"`
WorkspaceAccess bool `json:"workspace_access,omitempty"`
}

func (e entitlements) toComplexValueList() []ComplexValue {
result := []ComplexValue{}
if e.AllowClusterCreate {
result = append(result, ComplexValue{
Value: "allow-cluster-create",
})
}
if e.AllowInstancePoolCreate {
result = append(result, ComplexValue{
Value: "allow-instance-pool-create",
})
}
if e.DatabricksSQLAccess {
result = append(result, ComplexValue{
Value: "databricks-sql-access",
})
}
if e.WorkspaceAccess {
result = append(result, ComplexValue{
Value: "workspace-access",
})
}
return result
}

func newEntitlements(ctx context.Context, cv []ComplexValue) entitlements {
var e entitlements
for _, c := range cv {
switch c.Value {
case "allow-cluster-create":
e.AllowClusterCreate = true
case "allow-instance-pool-create":
e.AllowInstancePoolCreate = true
case "databricks-sql-access":
e.DatabricksSQLAccess = true
case "workspace-access":
e.WorkspaceAccess = true
default:
tflog.Info(ctx, fmt.Sprintf("Ignoring unknown entitlement: %s", c.Value))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should it be warning?

}
}
return e
}
2 changes: 1 addition & 1 deletion scim/groups.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func (a GroupsAPI) Patch(groupID string, r patchRequest) error {
return a.client.Scim(a.context, http.MethodPatch, fmt.Sprintf("/preview/scim/v2/Groups/%v", groupID), r, nil)
}

func (a GroupsAPI) UpdateNameAndEntitlements(groupID string, name string, externalID string, e entitlements) error {
func (a GroupsAPI) UpdateNameAndEntitlements(groupID string, name string, externalID string, e []ComplexValue) error {
g, err := a.Read(groupID, "displayName,entitlements,groups,members,externalId")
if err != nil {
return err
Expand Down
66 changes: 35 additions & 31 deletions scim/resource_entitlement.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,82 +9,86 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

type entitlementsResource struct {
entitlements
GroupId string `json:"group_id,omitempty" tf:"force_new"`
UserId string `json:"user_id,omitempty" tf:"force_new"`
SpnId string `json:"service_principal_id,omitempty" tf:"force_new"`
}

// ResourceGroup manages user groups
func ResourceEntitlements() common.Resource {
type entity struct {
GroupId string `json:"group_id,omitempty" tf:"force_new"`
UserId string `json:"user_id,omitempty" tf:"force_new"`
SpnId string `json:"service_principal_id,omitempty" tf:"force_new"`
}
entitlementSchema := common.StructToSchema(entity{},
entitlementSchema := common.StructToSchema(entitlementsResource{},
func(m map[string]*schema.Schema) map[string]*schema.Schema {
addEntitlementsToSchema(m)
alof := []string{"group_id", "user_id", "service_principal_id"}
for _, field := range alof {
m[field].AtLeastOneOf = alof
}
return m
})
addEntitlementsToSchema(entitlementSchema)
return common.Resource{
Create: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
if c.Config.IsAccountClient() {
return fmt.Errorf("entitlements can only be managed with a provider configured at the workspace-level")
}
err := patchEntitlements(ctx, d, c, "replace")
var e entitlementsResource
common.DataToStructPointer(d, entitlementSchema, &e)
err := patchEntitlements(ctx, e, c, "replace")
if err != nil {
return err
}
d.SetId(getId(d))
d.SetId(getId(e))
return nil
},
Read: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
split := strings.SplitN(d.Id(), "/", 2)
if len(split) != 2 {
return fmt.Errorf("ID must be two elements: %s", d.Id())
}
var entitlements entitlementsResource
switch strings.ToLower(split[0]) {
case "group":
group, err := NewGroupsAPI(ctx, c).Read(split[1], "entitlements")
if err != nil {
return err
}
d.Set("group_id", split[1])
group.Entitlements.generateEmpty(d)
return group.Entitlements.readIntoData(d)
entitlements.GroupId = split[1]
entitlements.entitlements = newEntitlements(ctx, group.Entitlements)
case "user":
user, err := NewUsersAPI(ctx, c).Read(split[1], "entitlements")
if err != nil {
return err
}
d.Set("user_id", split[1])
user.Entitlements.generateEmpty(d)
return user.Entitlements.readIntoData(d)
entitlements.UserId = split[1]
entitlements.entitlements = newEntitlements(ctx, user.Entitlements)
case "spn":
spn, err := NewServicePrincipalsAPI(ctx, c).Read(split[1], "entitlements")
if err != nil {
return err
}
d.Set("service_principal_id", split[1])
spn.Entitlements.generateEmpty(d)
return spn.Entitlements.readIntoData(d)
entitlements.SpnId = split[1]
entitlements.entitlements = newEntitlements(ctx, spn.Entitlements)
}
return nil
return common.StructToData(entitlements, entitlementSchema, d)
},
Update: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
return patchEntitlements(ctx, d, c, "replace")
var e entitlementsResource
common.DataToStructPointer(d, entitlementSchema, &e)
return patchEntitlements(ctx, e, c, "replace")
},
Delete: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
return patchEntitlements(ctx, d, c, "remove")
var e entitlementsResource
common.DataToStructPointer(d, entitlementSchema, &e)
return patchEntitlements(ctx, e, c, "remove")
},
Schema: entitlementSchema,
}
}

func getId(d *schema.ResourceData) string {
groupId := d.Get("group_id").(string)
userId := d.Get("user_id").(string)
spnId := d.Get("service_principal_id").(string)
func getId(e entitlementsResource) string {
groupId := e.GroupId
userId := e.UserId
spnId := e.SpnId
if groupId != "" {
return "group/" + groupId
}
Expand All @@ -97,12 +101,12 @@ func getId(d *schema.ResourceData) string {
return ""
}

func patchEntitlements(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient, op string) error {
groupId := d.Get("group_id").(string)
userId := d.Get("user_id").(string)
spnId := d.Get("service_principal_id").(string)
func patchEntitlements(ctx context.Context, e entitlementsResource, c *common.DatabricksClient, op string) error {
groupId := e.GroupId
userId := e.UserId
spnId := e.SpnId
noEntitlementMessage := "invalidPath No such attribute with the name : entitlements in the current resource"
entitlements := readEntitlementsFromData(d)
entitlements := e.toComplexValueList()
Copy link

Copilot AI Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method call should be e.entitlements.toComplexValueList() to access the embedded entitlements field, not called directly on the entitlementsResource struct.

Suggested change
entitlements := e.toComplexValueList()
entitlements := e.entitlements.toComplexValueList()

Copilot uses AI. Check for mistakes.
if len(entitlements) == 1 && entitlements[0].Value == "" && op == "remove" {
// No updates are needed, so return early
return nil
Expand Down
56 changes: 27 additions & 29 deletions scim/resource_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,37 +10,32 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

type groupResource struct {
entitlements
DisplayName string `json:"display_name"`
ExternalID string `json:"external_id,omitempty" tf:"force_new,suppress_diff"`
URL string `json:"url,omitempty" tf:"computed"`
AclPrincipalID string `json:"acl_principal_id,omitempty" tf:"computed"`
Force bool `json:"force,omitempty"`
}

// ResourceGroup manages user groups
func ResourceGroup() common.Resource {
type entity struct {
DisplayName string `json:"display_name"`
ExternalID string `json:"external_id,omitempty" tf:"force_new,suppress_diff"`
URL string `json:"url,omitempty" tf:"computed"`
}
groupSchema := common.StructToSchema(entity{},
groupSchema := common.StructToSchema(groupResource{},
func(m map[string]*schema.Schema) map[string]*schema.Schema {
addEntitlementsToSchema(m)
// https://github.com/databricks/terraform-provider-databricks/issues/1089
m["display_name"].ValidateDiagFunc = validation.ToDiagFunc(
validation.StringNotInSlice([]string{"users", "admins"}, false))
m["force"] = &schema.Schema{
Type: schema.TypeBool,
Optional: true,
}
m["acl_principal_id"] = &schema.Schema{
Type: schema.TypeString,
Optional: true,
Computed: true,
}
return m
})
addEntitlementsToSchema(groupSchema)
return common.Resource{
Create: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
var groupResource groupResource
common.DataToStructPointer(d, groupSchema, &groupResource)
g := Group{
DisplayName: d.Get("display_name").(string),
Entitlements: readEntitlementsFromData(d),
ExternalID: d.Get("external_id").(string),
DisplayName: groupResource.DisplayName,
Entitlements: groupResource.toComplexValueList(),
Copy link

Copilot AI Sep 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method call should be groupResource.entitlements.toComplexValueList() to access the embedded entitlements field, not called directly on groupResource.

Suggested change
Entitlements: groupResource.toComplexValueList(),
Entitlements: groupResource.entitlements.toComplexValueList(),

Copilot uses AI. Check for mistakes.
ExternalID: groupResource.ExternalID,
}
groupsAPI := NewGroupsAPI(ctx, c)
group, err := groupsAPI.Create(g)
Expand All @@ -55,20 +50,23 @@ func ResourceGroup() common.Resource {
if err != nil {
return err
}
d.Set("display_name", group.DisplayName)
d.Set("external_id", group.ExternalID)
d.Set("acl_principal_id", fmt.Sprintf("groups/%s", group.DisplayName))
groupResource := groupResource{
entitlements: newEntitlements(ctx, group.Entitlements),
DisplayName: group.DisplayName,
ExternalID: group.ExternalID,
AclPrincipalID: fmt.Sprintf("groups/%s", group.DisplayName),
}
if c.Config.IsAccountClient() {
d.Set("url", c.FormatURL("users/groups/", d.Id(), "/information"))
groupResource.URL = c.FormatURL("users/groups/", d.Id(), "/information")
} else {
d.Set("url", c.FormatURL("#setting/accounts/groups/", d.Id()))
groupResource.URL = c.FormatURL("#setting/accounts/groups/", d.Id())
}
return group.Entitlements.readIntoData(d)
return common.StructToData(groupResource, groupSchema, d)
},
Update: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
groupName := d.Get("display_name").(string)
return NewGroupsAPI(ctx, c).UpdateNameAndEntitlements(d.Id(), groupName,
d.Get("external_id").(string), readEntitlementsFromData(d))
var groupResource groupResource
common.DataToStructPointer(d, groupSchema, &groupResource)
return NewGroupsAPI(ctx, c).UpdateNameAndEntitlements(d.Id(), groupResource.DisplayName, groupResource.ExternalID, groupResource.entitlements.toComplexValueList())
},
Delete: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error {
return NewGroupsAPI(ctx, c).Delete(d.Id())
Expand Down
4 changes: 2 additions & 2 deletions scim/resource_group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ func TestResourceGroupUpdate(t *testing.T) {
Resource: "/api/2.0/preview/scim/v2/Groups/abc",
ExpectedRequest: Group{
DisplayName: "Data Ninjas",
Entitlements: entitlements{
Entitlements: []ComplexValue{
{
Value: "allow-cluster-create",
},
Expand Down Expand Up @@ -260,7 +260,7 @@ func TestResourceGroupUpdate(t *testing.T) {
Resource: "/api/2.0/preview/scim/v2/Groups/abc?attributes=displayName,externalId,entitlements",
Response: Group{
DisplayName: "Data Ninjas",
Entitlements: entitlements{
Entitlements: []ComplexValue{
{
Value: "allow-cluster-create",
},
Expand Down
Loading
Loading