From b4bb45230bf6bd407d38bd16e09870d6777f430c Mon Sep 17 00:00:00 2001 From: Alex Ott Date: Thu, 18 Nov 2021 13:15:01 +0100 Subject: [PATCH] Add optional external_id field to SCIM users and groups (#927) External ID could be propagated from linked identity provider, and is used for things like Unity Catalog --- docs/data-sources/current_user.md | 1 + docs/data-sources/group.md | 1 + docs/data-sources/user.md | 1 + docs/resources/group.md | 1 + docs/resources/user.md | 1 + identity/data_current_user.go | 5 +++++ identity/data_group.go | 2 ++ identity/data_user.go | 10 ++++++++++ identity/groups.go | 3 ++- identity/resource_group.go | 10 +++++++++- identity/resource_user.go | 3 +++ identity/scim.go | 2 ++ 12 files changed, 38 insertions(+), 2 deletions(-) diff --git a/docs/data-sources/current_user.md b/docs/data-sources/current_user.md index fbd1a88ce1..4c74478fde 100644 --- a/docs/data-sources/current_user.md +++ b/docs/data-sources/current_user.md @@ -58,6 +58,7 @@ output "job_url" { Data source exposes the following attributes: * `id` - The id of the calling user. +* `external_id` - ID of the user in an external identity provider. * `user_name` - Name of the [user](../resources/user.md), e.g. `mr.foo@example.com`. * `home` - Home folder of the [user](../resources/user.md), e.g. `/Users/mr.foo@example.com`. * `repos` - Personal Repos location of the [user](../resources/user.md), e.g. `/Repos/mr.foo@example.com`. diff --git a/docs/data-sources/group.md b/docs/data-sources/group.md index 27e0c7f19b..8fff4f3ffb 100644 --- a/docs/data-sources/group.md +++ b/docs/data-sources/group.md @@ -38,6 +38,7 @@ Data source allows you to pick groups by the following attributes Data source exposes the following attributes: * `id` - The id for the group object. +* `external_id` - ID of the group in an external identity provider. * `members` - Set of [user](../resources/user.md) identifiers, that can be modified with [databricks_group_member](../resources/group_member.md) resource. * `groups` - Set of [group](../resources/group.md) identifiers, that can be modified with [databricks_group_member](../resources/group_member.md) resource. * `instance_profiles` - Set of [instance profile](../resources/instance_profile.md) ARNs, that can be modified by [databricks_group_instance_profile](../resources/group_instance_profile.md) resource. diff --git a/docs/data-sources/user.md b/docs/data-sources/user.md index 7bb122cb09..f1349ab808 100644 --- a/docs/data-sources/user.md +++ b/docs/data-sources/user.md @@ -39,6 +39,7 @@ Data source allows you to pick groups by the following attributes Data source exposes the following attributes: - `id` - The id of the user. +- `external_id` - ID of the user in an external identity provider. - `user_name` - Name of the [user](../resources/user.md), e.g. `mr.foo@example.com`. - `display_name` - Display name of the [user](../resources/user.md), e.g. `Mr Foo`. - `home` - Home folder of the [user](../resources/user.md), e.g. `/Users/mr.foo@example.com`. diff --git a/docs/resources/group.md b/docs/resources/group.md index e7ed205187..89dcc5fff6 100644 --- a/docs/resources/group.md +++ b/docs/resources/group.md @@ -47,6 +47,7 @@ resource "databricks_group_member" "vip_member" { The following arguments are supported: * `display_name` - (Required) This is the display name for the given group. +* `external_id` - (Optional) ID of the group in an external identity provider. * `allow_cluster_create` - (Optional) This is a field to allow the group to have [cluster](cluster.md) create privileges. More fine grained permissions could be assigned with [databricks_permissions](permissions.md#Cluster-usage) and [cluster_id](permissions.md#cluster_id) argument. Everyone without `allow_cluster_create` argument set, but with [permission to use](permissions.md#Cluster-Policy-usage) Cluster Policy would be able to create clusters, but within boundaries of that specific policy. * `allow_instance_pool_create` - (Optional) This is a field to allow the group to have [instance pool](instance_pool.md) create privileges. More fine grained permissions could be assigned with [databricks_permissions](permissions.md#Instance-Pool-usage) and [instance_pool_id](permissions.md#instance_pool_id) argument. * `allow_sql_analytics_access` - (Optional) This is a field to allow the group to have access to [Databricks SQL](https://databricks.com/product/databricks-sql) feature through [databricks_sql_endpoint](sql_endpoint.md). diff --git a/docs/resources/user.md b/docs/resources/user.md index c5dab72e9a..71b9d7532c 100644 --- a/docs/resources/user.md +++ b/docs/resources/user.md @@ -48,6 +48,7 @@ The following arguments are available: * `user_name` - (Required) This is the username of the given user and will be their form of access and identity. * `display_name` - (Optional) This is an alias for the username that can be the full name of the user. +* `external_id` - (Optional) ID of the user in an external identity provider. * `allow_cluster_create` - (Optional) Allow the user to have [cluster](cluster.md) create privileges. Defaults to false. More fine grained permissions could be assigned with [databricks_permissions](permissions.md#Cluster-usage) and `cluster_id` argument. Everyone without `allow_cluster_create` argument set, but with [permission to use](permissions.md#Cluster-Policy-usage) Cluster Policy would be able to create clusters, but within boundaries of that specific policy. * `allow_instance_pool_create` - (Optional) Allow the user to have [instance pool](instance_pool.md) create privileges. Defaults to false. More fine grained permissions could be assigned with [databricks_permissions](permissions.md#Instance-Pool-usage) and [instance_pool_id](permissions.md#instance_pool_id) argument. * `allow_sql_analytics_access` - (Optional) This is a field to allow the group to have access to [Databricks SQL](https://databricks.com/product/sql-analytics) feature through [databricks_sql_endpoint](sql_endpoint.md). diff --git a/identity/data_current_user.go b/identity/data_current_user.go index f627518dcb..7fe1f8f371 100644 --- a/identity/data_current_user.go +++ b/identity/data_current_user.go @@ -32,6 +32,10 @@ func DataSourceCurrentUser() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "external_id": { + Type: schema.TypeString, + Computed: true, + }, }, ReadContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { usersAPI := NewUsersAPI(ctx, m) @@ -42,6 +46,7 @@ func DataSourceCurrentUser() *schema.Resource { d.Set("user_name", me.UserName) d.Set("home", fmt.Sprintf("/Users/%s", me.UserName)) d.Set("repos", fmt.Sprintf("/Repos/%s", me.UserName)) + d.Set("external_id", me.ExternalID) splits := strings.Split(me.UserName, "@") norm := nonAlphanumeric.ReplaceAllLiteralString(splits[0], "_") norm = strings.ToLower(norm) diff --git a/identity/data_group.go b/identity/data_group.go index 57b4287877..e1207c6d46 100644 --- a/identity/data_group.go +++ b/identity/data_group.go @@ -18,6 +18,7 @@ func DataSourceGroup() *schema.Resource { Members []string `json:"members,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"` } s := common.StructToSchema(entity{}, func( @@ -65,6 +66,7 @@ func DataSourceGroup() *schema.Resource { } } } + this.ExternalID = group.ExternalID sort.Strings(this.Groups) sort.Strings(this.Members) sort.Strings(this.InstanceProfiles) diff --git a/identity/data_user.go b/identity/data_user.go index d88258833e..015b058e22 100644 --- a/identity/data_user.go +++ b/identity/data_user.go @@ -43,6 +43,10 @@ func DataSourceUser() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "repos": { + Type: schema.TypeString, + Computed: true, + }, "display_name": { Type: schema.TypeString, Computed: true, @@ -51,6 +55,10 @@ func DataSourceUser() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "external_id": { + Type: schema.TypeString, + Computed: true, + }, }, ReadContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { usersAPI := NewUsersAPI(ctx, m) @@ -61,6 +69,8 @@ func DataSourceUser() *schema.Resource { d.Set("user_name", user.UserName) d.Set("display_name", user.DisplayName) d.Set("home", fmt.Sprintf("/Users/%s", user.UserName)) + d.Set("repos", fmt.Sprintf("/Repos/%s", user.UserName)) + d.Set("external_id", user.ExternalID) splits := strings.Split(user.UserName, "@") norm := nonAlphanumeric.ReplaceAllLiteralString(splits[0], "_") norm = strings.ToLower(norm) diff --git a/identity/groups.go b/identity/groups.go index 138f1fa4b7..872527d9df 100644 --- a/identity/groups.go +++ b/identity/groups.go @@ -66,7 +66,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, e entitlements) error { +func (a GroupsAPI) UpdateNameAndEntitlements(groupID string, name string, externalID string, e entitlements) error { g, err := a.Read(groupID) if err != nil { return err @@ -80,6 +80,7 @@ func (a GroupsAPI) UpdateNameAndEntitlements(groupID string, name string, e enti Roles: g.Roles, Members: g.Members, Schemas: []URN{GroupSchema}, + ExternalID: externalID, }, nil) } diff --git a/identity/resource_group.go b/identity/resource_group.go index 8d499019ed..d9fdef76cf 100644 --- a/identity/resource_group.go +++ b/identity/resource_group.go @@ -19,6 +19,11 @@ func ResourceGroup() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "external_id": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, } addEntitlementsToSchema(&groupSchema) return common.Resource{ @@ -27,6 +32,7 @@ func ResourceGroup() *schema.Resource { group, err := NewGroupsAPI(ctx, c).Create(ScimGroup{ DisplayName: groupName, Entitlements: readEntitlementsFromData(d), + ExternalID: d.Get("external_id").(string), }) if err != nil { return err @@ -40,12 +46,14 @@ func ResourceGroup() *schema.Resource { return err } d.Set("display_name", group.DisplayName) + d.Set("external_id", group.ExternalID) d.Set("url", c.FormatURL("#setting/accounts/groups/", d.Id())) return group.Entitlements.readIntoData(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, readEntitlementsFromData(d)) + return NewGroupsAPI(ctx, c).UpdateNameAndEntitlements(d.Id(), groupName, + d.Get("external_id").(string), readEntitlementsFromData(d)) }, Delete: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error { return NewGroupsAPI(ctx, c).Delete(d.Id()) diff --git a/identity/resource_user.go b/identity/resource_user.go index 80f3c3f0da..bbcc227cd7 100644 --- a/identity/resource_user.go +++ b/identity/resource_user.go @@ -14,6 +14,7 @@ func ResourceUser() *schema.Resource { UserName string `json:"user_name" tf:"force_new"` DisplayName string `json:"display_name,omitempty" tf:"computed"` Active bool `json:"active,omitempty"` + ExternalID string `json:"external_id,omitempty"` } userSchema := common.StructToSchema(entity{}, func(m map[string]*schema.Schema) map[string]*schema.Schema { @@ -31,6 +32,7 @@ func ResourceUser() *schema.Resource { DisplayName: u.DisplayName, Active: u.Active, Entitlements: readEntitlementsFromData(d), + ExternalID: u.ExternalID, }, nil } return common.Resource{ @@ -55,6 +57,7 @@ func ResourceUser() *schema.Resource { d.Set("user_name", user.UserName) d.Set("display_name", user.DisplayName) d.Set("active", user.Active) + d.Set("external_id", user.ExternalID) return user.Entitlements.readIntoData(d) }, Update: func(ctx context.Context, d *schema.ResourceData, c *common.DatabricksClient) error { diff --git a/identity/scim.go b/identity/scim.go index a7a83bf4d5..1be4971016 100644 --- a/identity/scim.go +++ b/identity/scim.go @@ -98,6 +98,7 @@ type ScimGroup struct { Groups []ComplexValue `json:"groups,omitempty"` Roles []ComplexValue `json:"roles,omitempty"` Entitlements entitlements `json:"entitlements,omitempty"` + ExternalID string `json:"externalId,omitempty"` } // GroupList contains a list of groups fetched from a list api call from SCIM api @@ -128,6 +129,7 @@ type ScimUser struct { Name map[string]string `json:"name,omitempty"` Roles []ComplexValue `json:"roles,omitempty"` Entitlements entitlements `json:"entitlements,omitempty"` + ExternalID string `json:"externalId,omitempty"` } // UserList contains a list of Users fetched from a list api call from SCIM api