Skip to content

Commit a121b39

Browse files
Added databricks_service_principal and databricks_service_principals data resources (#1370)
* return `application_id` from `databricks_current_user` if that user is a Service Principal * add `databricks_service_principal` data source based on `application_id` * add `databricks_service_principals` data source based on `display_name` Co-authored-by: Ron DeFreitas <[email protected]>
1 parent aa03042 commit a121b39

File tree

9 files changed

+400
-0
lines changed

9 files changed

+400
-0
lines changed

docs/data-sources/current_user.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ output "job_url" {
5656
Data source exposes the following attributes:
5757

5858
* `id` - The id of the calling user.
59+
* `application_id` - Application ID of the [service principal](../resources/service_principal.md) if the currently logged-in user is a service principal, e.g. `11111111-2222-3333-4444-555666777888`
5960
* `external_id` - ID of the user in an external identity provider.
6061
* `user_name` - Name of the [user](../resources/user.md), e.g. `[email protected]`.
6162
* `home` - Home folder of the [user](../resources/user.md), e.g. `/Users/[email protected]`.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
subcategory: "Security"
3+
---
4+
5+
# databricks_service_principal Data Source
6+
7+
-> **Note** If you have a fully automated setup with workspaces created by [databricks_mws_workspaces](../resources/mws_workspaces.md) or [azurerm_databricks_workspace](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/databricks_workspace), please make sure to add [depends_on attribute](../index.md#data-resources-and-authentication-is-not-configured-errors) in order to prevent _authentication is not configured for provider_ errors.
8+
9+
Retrieves information about [databricks_service_principal](../resources/service_principal.md).
10+
11+
## Example Usage
12+
13+
Adding service principal `11111111-2222-3333-4444-555666777888` to administrative group
14+
15+
```hcl
16+
data "databricks_group" "admins" {
17+
display_name = "admins"
18+
}
19+
20+
data "databricks_service_principal" "spn" {
21+
application_id = "11111111-2222-3333-4444-555666777888"
22+
}
23+
24+
resource "databricks_group_member" "my_member_a" {
25+
group_id = data.databricks_group.admins.id
26+
member_id = data.databricks_service_principal.spn.id
27+
}
28+
```
29+
30+
## Argument Reference
31+
32+
Data source allows you to pick service principals by the following attributes
33+
34+
- `application_id` - (Required) ID of the service principal. The service principal must exist before this resource can be retrieved.
35+
36+
## Attribute Reference
37+
38+
Data source exposes the following attributes:
39+
40+
- `sp_id` - The id of the service principal.
41+
- `external_id` - ID of the service principal in an external identity provider.
42+
- `display_name` - Display name of the [service principal](../resources/service_principal.md), e.g. `Foo SPN`.
43+
- `home` - Home folder of the [service principal](../resources/service_principal.md), e.g. `/Users/11111111-2222-3333-4444-555666777888`.
44+
- `repos` - Repos location of the [service principal](../resources/service_principal.md), e.g. `/Repos/11111111-2222-3333-4444-555666777888`.
45+
- `active` - Whether service principal is active or not.
46+
47+
## Related Resources
48+
49+
The following resources are used in the same context:
50+
51+
* [End to end workspace management](../guides/passthrough-cluster-per-user.md) guide
52+
* [databricks_current_user](current_user.md) data to retrieve information about [databricks_user](../resources/user.md) or [databricks_service_principal](../resources/service_principal.md), that is calling Databricks REST API.
53+
* [databricks_group](../resources/group.md) to manage [groups in Databricks Workspace](https://docs.databricks.com/administration-guide/users-groups/groups.html) or [Account Console](https://accounts.cloud.databricks.com/) (for AWS deployments).
54+
* [databricks_group](group.md) data to retrieve information about [databricks_group](../resources/group.md) members, entitlements and instance profiles.
55+
* [databricks_group_instance_profile](../resources/group_instance_profile.md) to attach [databricks_instance_profile](../resources/instance_profile.md) (AWS) to [databricks_group](../resources/group.md).
56+
* [databricks_group_member](../resources/group_member.md) to attach [users](../resources/user.md) and [groups](../resources/group.md) as group members.
57+
* [databricks_permissions](../resources/permissions.md) to manage [access control](https://docs.databricks.com/security/access-control/index.html) in Databricks workspace.
58+
* [databricks_service principal](../resources/service_principal.md) to manage [service principals](../resources/service_principal.md)
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
---
2+
subcategory: "Security"
3+
---
4+
5+
# databricks_service_principals Data Source
6+
7+
-> **Note** If you have a fully automated setup with workspaces created by [databricks_mws_workspaces](../resources/mws_workspaces.md) or [azurerm_databricks_workspace](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/databricks_workspace), please make sure to add [depends_on attribute](../index.md#data-resources-and-authentication-is-not-configured-errors) in order to prevent _authentication is not configured for provider_ errors.
8+
9+
Retrieves `application_ids` of all [databricks_service_principal](../resources/service_principal.md) based on their `display_name`
10+
11+
## Example Usage
12+
13+
Adding all service principals of which display name contains `my-spn` to admin group
14+
15+
```hcl
16+
data "databricks_group" "admins" {
17+
display_name = "admins"
18+
}
19+
20+
data "databricks_service_principals" "spns" {
21+
display_name = "my-spn"
22+
}
23+
24+
data "databricks_service_principal" "spn" {
25+
for_each = toset(data.databricks_service_principals.spns.application_ids)
26+
application_id = each.value
27+
}
28+
29+
resource "databricks_group_member" "my_member_spn" {
30+
for_each = toset(data.databricks_service_principals.spns.application_ids)
31+
group_id = data.databricks_group.admins.id
32+
member_id = data.databricks_service_principal.spn[each.value].sp_id
33+
}
34+
```
35+
36+
## Argument Reference
37+
38+
Data source allows you to pick service principals by the following attributes
39+
40+
- `display_name_contains` - (Optional) Only return [databricks_service_principal](databricks_service_principal.md) display name that match the given name string
41+
42+
## Attribute Reference
43+
44+
Data source exposes the following attributes:
45+
46+
- `application_ids` - List of `application_ids` of service principals Individual service principal can be retrieved using [databricks_service_principal](databricks_service_principal.md) data source
47+
48+
## Related Resources
49+
50+
The following resources are used in the same context:
51+
52+
* [End to end workspace management](../guides/passthrough-cluster-per-user.md) guide
53+
* [databricks_current_user](current_user.md) data to retrieve information about [databricks_user](../resources/user.md) or [databricks_service_principal](../resources/service_principal.md), that is calling Databricks REST API.
54+
* [databricks_group](../resources/group.md) to manage [groups in Databricks Workspace](https://docs.databricks.com/administration-guide/users-groups/groups.html) or [Account Console](https://accounts.cloud.databricks.com/) (for AWS deployments).
55+
* [databricks_group](group.md) data to retrieve information about [databricks_group](../resources/group.md) members, entitlements and instance profiles.
56+
* [databricks_group_instance_profile](../resources/group_instance_profile.md) to attach [databricks_instance_profile](../resources/instance_profile.md) (AWS) to [databricks_group](../resources/group.md).
57+
* [databricks_group_member](../resources/group_member.md) to attach [users](../resources/user.md) and [groups](../resources/group.md) as group members.
58+
* [databricks_permissions](../resources/permissions.md) to manage [access control](https://docs.databricks.com/security/access-control/index.html) in Databricks workspace.
59+
* [databricks_service principal](../resources/service_principal.md) to manage [service principals](../resources/service_principal.md)

provider/provider.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ func DatabricksProvider() *schema.Provider {
5050
"databricks_notebook": workspace.DataSourceNotebook(),
5151
"databricks_notebook_paths": workspace.DataSourceNotebookPaths(),
5252
"databricks_schemas": catalog.DataSourceSchemas(),
53+
"databricks_service_principal": scim.DataSourceServicePrincipal(),
54+
"databricks_service_principals": scim.DataSourceServicePrincipals(),
5355
"databricks_spark_version": clusters.DataSourceSparkVersion(),
5456
"databricks_tables": catalog.DataSourceTables(),
5557
"databricks_views": catalog.DataSourceViews(),

scim/data_service_principal.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package scim
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/databrickslabs/terraform-provider-databricks/common"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
9+
)
10+
11+
// DataSourceServicePrincipal returns information about the spn specified by the application_id
12+
func DataSourceServicePrincipal() *schema.Resource {
13+
type spnData struct {
14+
ApplicationID string `json:"application_id,omitempty" tf:"computed"`
15+
DisplayName string `json:"display_name,omitempty" tf:"computed"`
16+
SpID string `json:"sp_id,omitempty" tf:"computed"`
17+
Home string `json:"home,omitempty" tf:"computed"`
18+
Repos string `json:"repos,omitempty" tf:"computed"`
19+
Active bool `json:"active,omitempty" tf:"computed"`
20+
ExternalID string `json:"external_id,omitempty" tf:"computed"`
21+
}
22+
return common.DataResource(spnData{}, func(ctx context.Context, e interface{}, c *common.DatabricksClient) error {
23+
response := e.(*spnData)
24+
spnAPI := NewServicePrincipalsAPI(ctx, c)
25+
spList, err := spnAPI.filter(fmt.Sprintf("applicationId eq '%s'", response.ApplicationID))
26+
if err != nil {
27+
return err
28+
}
29+
if len(spList) == 0 {
30+
return fmt.Errorf("cannot find SP with ID %s", response.ApplicationID)
31+
}
32+
sp := spList[0]
33+
response.DisplayName = sp.DisplayName
34+
response.Home = fmt.Sprintf("/Users/%s", sp.ApplicationID)
35+
response.Repos = fmt.Sprintf("/Repos/%s", sp.ApplicationID)
36+
response.ExternalID = sp.ExternalID
37+
response.Active = sp.Active
38+
response.SpID = sp.ID
39+
return nil
40+
})
41+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package scim
2+
3+
import (
4+
"testing"
5+
6+
"github.com/databrickslabs/terraform-provider-databricks/qa"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestDataServicePrincipalReadByAppId(t *testing.T) {
11+
qa.ResourceFixture{
12+
Fixtures: []qa.HTTPFixture{
13+
{
14+
Method: "GET",
15+
Resource: "/api/2.0/preview/scim/v2/ServicePrincipals?filter=applicationId%20eq%20%27abc%27",
16+
Response: UserList{
17+
Resources: []User{
18+
{
19+
ID: "abc",
20+
DisplayName: "Example Service Principal",
21+
Active: true,
22+
ApplicationID: "abc",
23+
Groups: []ComplexValue{
24+
{
25+
Display: "admins",
26+
Value: "4567",
27+
},
28+
{
29+
Display: "ds",
30+
Value: "9877",
31+
},
32+
},
33+
},
34+
},
35+
},
36+
},
37+
},
38+
Resource: DataSourceServicePrincipal(),
39+
HCL: `application_id = "abc"`,
40+
Read: true,
41+
NonWritable: true,
42+
ID: "_",
43+
}.ApplyAndExpectData(t, map[string]interface{}{
44+
"sp_id": "abc",
45+
"application_id": "abc",
46+
"display_name": "Example Service Principal",
47+
"active": true,
48+
"home": "/Users/abc",
49+
"repos": "/Repos/abc",
50+
})
51+
}
52+
53+
func TestDataServicePrincipalReadNotFound(t *testing.T) {
54+
_, err := qa.ResourceFixture{
55+
Fixtures: []qa.HTTPFixture{
56+
{
57+
Method: "GET",
58+
Resource: "/api/2.0/preview/scim/v2/ServicePrincipals?filter=applicationId%20eq%20%27abc%27",
59+
Response: UserList{},
60+
},
61+
},
62+
Resource: DataSourceServicePrincipal(),
63+
HCL: `application_id = "abc"`,
64+
Read: true,
65+
NonWritable: true,
66+
ID: "_",
67+
}.Apply(t)
68+
require.Error(t, err, err)
69+
}
70+
71+
func TestDataServicePrincipalReadError(t *testing.T) {
72+
_, err := qa.ResourceFixture{
73+
Fixtures: []qa.HTTPFixture{
74+
{
75+
Method: "GET",
76+
Resource: "/api/2.0/preview/scim/v2/ServicePrincipals?filter=applicationId%20eq%20%27abc%27",
77+
Status: 500,
78+
},
79+
},
80+
Resource: DataSourceServicePrincipal(),
81+
HCL: `application_id = "abc"`,
82+
Read: true,
83+
NonWritable: true,
84+
ID: "_",
85+
}.Apply(t)
86+
require.Error(t, err, err)
87+
}

scim/data_service_principals.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package scim
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"sort"
7+
8+
"github.com/databrickslabs/terraform-provider-databricks/common"
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
)
11+
12+
// DataSourceServicePrincipals searches for service principals based on display_name
13+
func DataSourceServicePrincipals() *schema.Resource {
14+
type spnsData struct {
15+
DisplayNameContains string `json:"display_name_contains,omitempty" tf:"computed"`
16+
ApplicationIDs []string `json:"application_ids,omitempty" tf:"computed,slice_set"`
17+
}
18+
return common.DataResource(spnsData{}, func(ctx context.Context, e interface{}, c *common.DatabricksClient) error {
19+
response := e.(*spnsData)
20+
spnAPI := NewServicePrincipalsAPI(ctx, c)
21+
22+
spList, err := spnAPI.filter(fmt.Sprintf("displayName co '%s'", response.DisplayNameContains))
23+
if err != nil {
24+
return err
25+
}
26+
if len(spList) == 0 {
27+
return fmt.Errorf("cannot find SPs with display name containing %s", response.DisplayNameContains)
28+
}
29+
for _, sp := range spList {
30+
response.ApplicationIDs = append(response.ApplicationIDs, sp.ApplicationID)
31+
}
32+
sort.Strings(response.ApplicationIDs)
33+
return nil
34+
})
35+
}

0 commit comments

Comments
 (0)