diff --git a/docs/data-sources/group.md b/docs/data-sources/group.md index aa6481cd36..ee8938bf4a 100644 --- a/docs/data-sources/group.md +++ b/docs/data-sources/group.md @@ -53,6 +53,7 @@ Data source exposes the following attributes: The following resources are used in the same context: * [End to end workspace management](../guides/workspace-management.md) guide +* [databricks_groups](../data-sources/groups.md) to retrive [Groups](https://docs.databricks.com/en/admin/users-groups/groups.html) information. * [databricks_cluster](../resources/cluster.md) to create [Databricks Clusters](https://docs.databricks.com/clusters/index.html). * [databricks_directory](../resources/directory.md) to manage directories in [Databricks Workpace](https://docs.databricks.com/workspace/workspace-objects.html). * [databricks_group_member](../resources/group_member.md) to attach [users](../resources/user.md) and [groups](../resources/group.md) as group members. diff --git a/docs/data-sources/groups.md b/docs/data-sources/groups.md new file mode 100644 index 0000000000..d7a217e268 --- /dev/null +++ b/docs/data-sources/groups.md @@ -0,0 +1,58 @@ +--- +subcategory: "Security" +--- +# databricks_groups Data Source + +Retrieves a list of all [databricks_group](../resources/group.md) display names associated with the workspace, or those matching the provided filter. Maximum 100 results. + + +## Example Usage + +Get all groups: + +```hcl +data "databricks_groups" "all" {} + +output "all_groups" { + value = data.databricksdatabricks_groups.all.display_names +} +``` + +Get groups whose displayName contains `foo` or displayName contains `bar`.: + +```hcl +data "databricks_groups" "this" { + filter = "displayName co \"foo\" or displayName co \"bar\"" +} + +output "foobar_groups" { + value = data.databricks_pipelines.this.display_names +} +``` + + +## Argument Reference + +The following arguments are supported: + +* `filter` - (Optional) Query by which the results have to be filtered. See [API reference](https://docs.databricks.com/api/workspace/groups/list#filter). + + +## Attribute Reference + +Data source exposes the following attributes: + +* `display_names` - List of display names for [Groups](https://docs.databricks.com/data-engineering/delta-live-tables/index.html) matching the provided search criteria. + + +## Related Resources + +The following resources are used in the same context: + +* [End to end workspace management](../guides/workspace-management.md) guide +* [databricks_group](../data-sources/group.md) to retrive specific [Group](https://docs.databricks.com/en/admin/users-groups/groups.html) information. +* [databricks_cluster](../resources/cluster.md) to create [Databricks Clusters](https://docs.databricks.com/clusters/index.html). +* [databricks_directory](../resources/directory.md) to manage directories in [Databricks Workpace](https://docs.databricks.com/workspace/workspace-objects.html). +* [databricks_group_member](../resources/group_member.md) to attach [users](../resources/user.md) and [groups](../resources/group.md) as group members. +* [databricks_permissions](../resources/permissions.md) to manage [access control](https://docs.databricks.com/security/access-control/index.html) in Databricks workspace. +* [databricks_user](../resources/user.md) to [manage users](https://docs.databricks.com/administration-guide/users-groups/users.html), that could be added to [databricks_group](../resources/group.md) within the workspace. diff --git a/internal/acceptance/data_groups_test.go b/internal/acceptance/data_groups_test.go new file mode 100644 index 0000000000..d08ddb86da --- /dev/null +++ b/internal/acceptance/data_groups_test.go @@ -0,0 +1,67 @@ +package acceptance + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const groupsDataSourceTemplate = ` +resource "databricks_group" "group1" { + display_name = "tf-group-{var.RANDOM}" +} +resource "databricks_group" "group2" { + display_name = "tf-groupfoo-{var.RANDOM}" +} +resource "databricks_group" "group3" { + display_name = "tf-groupbar-{var.RANDOM}" +} + +#data "databricks_groups" "all" { + depends_on = [ + databricks_group.group1, + databricks_group.group2, + databricks_group.group3 + ] +} + +data "databricks_groups" "this" { + filter = "displayName co \"foo\" or displayName co \"bar\"" + depends_on = [ + databricks_group.group1, + databricks_group.group2, + databricks_group.group3 + ] +} +` + +func checkGroupsDataSourcePopulated(t *testing.T) func(s *terraform.State) error { + return func(s *terraform.State) error { + r_all, ok := s.Modules[0].Resources["data.databricks_groups.all"] + require.True(t, ok, "data.databricks_group.all has to be there") + attr := r_all.Primary.Attributes + assert.Equal(t, attr["display_names.#"], "3") + + r_filtered, ok := s.Modules[0].Resources["data.databricks_groups.this"] + require.True(t, ok, "data.databricks_group.this has to be there") + attr_filtered := r_filtered.Primary.Attributes + assert.Equal(t, attr_filtered["display_names.#"], "2") + + return nil + } +} + +func TestAccDataSourceGroups(t *testing.T) { + accountLevel(t, step{ + Template: groupsDataSourceTemplate, + Check: checkGroupsDataSourcePopulated(t), + }) +} +func TestMwsAccDataSourceGroups(t *testing.T) { + workspaceLevel(t, step{ + Template: groupsDataSourceTemplate, + Check: checkGroupsDataSourcePopulated(t), + }) +} diff --git a/internal/providers/sdkv2/sdkv2.go b/internal/providers/sdkv2/sdkv2.go index b9ee686121..2418452794 100644 --- a/internal/providers/sdkv2/sdkv2.go +++ b/internal/providers/sdkv2/sdkv2.go @@ -91,6 +91,7 @@ func DatabricksProvider() *schema.Provider { "databricks_external_location": catalog.DataSourceExternalLocation().ToResource(), "databricks_external_locations": catalog.DataSourceExternalLocations().ToResource(), "databricks_group": scim.DataSourceGroup().ToResource(), + "databricks_groups": scim.DataSourceGroups().ToResource(), "databricks_instance_pool": pools.DataSourceInstancePool().ToResource(), "databricks_instance_profiles": aws.DataSourceInstanceProfiles().ToResource(), "databricks_jobs": jobs.DataSourceJobs().ToResource(), diff --git a/scim/data_group.go b/scim/data_group.go index fc465492b4..274776c1a1 100644 --- a/scim/data_group.go +++ b/scim/data_group.go @@ -13,7 +13,7 @@ import ( // DataSourceGroup returns information about group specified by display name func DataSourceGroup() common.Resource { - type entity struct { + type groupData struct { DisplayName string `json:"display_name"` Recursive bool `json:"recursive,omitempty"` Members []string `json:"members,omitempty" tf:"slice_set,computed"` @@ -26,7 +26,7 @@ func DataSourceGroup() common.Resource { 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 @@ -39,7 +39,7 @@ func DataSourceGroup() common.Resource { 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" diff --git a/scim/data_groups.go b/scim/data_groups.go new file mode 100644 index 0000000000..0e05ec1f32 --- /dev/null +++ b/scim/data_groups.go @@ -0,0 +1,32 @@ +package scim + +import ( + "context" + + "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/service/iam" + "github.com/databricks/terraform-provider-databricks/common" +) + +// DataSourceGroups searches from groups based on filter +func DataSourceGroups() common.Resource { + type groupData struct { + DisplayNameFilter string `json:"filter,omitempty"` + DisplayNames []string `json:"display_names,omitempty" tf:"computed,slice_set"` + } + + return common.WorkspaceData(func(ctx context.Context, data *groupData, w *databricks.WorkspaceClient) error { + groupSearch := iam.ListGroupsRequest{Attributes: "displayName", Count: 100} + groupSearch.Filter = data.DisplayNameFilter + + groups, err := w.Groups.ListAll(ctx, groupSearch) + if err != nil { + return err + } + data.DisplayNames = make([]string, 0, len(groups)) + for _, v := range groups { + data.DisplayNames = append(data.DisplayNames, v.DisplayName) + } + return nil + }) +} diff --git a/scim/data_groups_test.go b/scim/data_groups_test.go new file mode 100644 index 0000000000..f89285f6f0 --- /dev/null +++ b/scim/data_groups_test.go @@ -0,0 +1,62 @@ +package scim + +import ( + "testing" + + "github.com/databricks/databricks-sdk-go/experimental/mocks" + "github.com/databricks/databricks-sdk-go/service/iam" + "github.com/databricks/terraform-provider-databricks/qa" + "github.com/stretchr/testify/mock" +) + +func TestDataSourceGroups_Error(t *testing.T) { + qa.ResourceFixture{ + Fixtures: qa.HTTPFailures, + Resource: DataSourceGroups(), + Read: true, + NonWritable: true, + ID: "_", + }.ExpectError(t, "i'm a teapot") +} + +func TestDataSourceGroups(t *testing.T) { + qa.ResourceFixture{ + MockWorkspaceClientFunc: func(m *mocks.MockWorkspaceClient) { + e := m.GetMockGroupsAPI().EXPECT() + e.ListAll(mock.Anything, iam.ListGroupsRequest{Attributes: "displayName", Count: 100}).Return( + []iam.Group{ + {DisplayName: "ds"}, {DisplayName: "product"}, + }, nil) + }, + Resource: DataSourceGroups(), + Read: true, + NonWritable: true, + ID: "_", + }.ApplyAndExpectData(t, map[string]any{ + "display_names": []string{ + "ds", + "product", + }, + }) +} + +func TestDataSourceGroups_Filter(t *testing.T) { + qa.ResourceFixture{ + MockWorkspaceClientFunc: func(m *mocks.MockWorkspaceClient) { + e := m.GetMockGroupsAPI().EXPECT() + e.ListAll(mock.Anything, iam.ListGroupsRequest{Attributes: "displayName", Count: 100, Filter: "displayName sw \"prod\""}).Return( + []iam.Group{ + {DisplayName: "product"}, + }, nil) + }, + Resource: DataSourceGroups(), + HCL: `filter="displayName sw \"prod\""`, + Read: true, + NonWritable: true, + ID: "_", + }.ApplyAndExpectData(t, map[string]any{ + "display_names": []string{ + "product", + }, + }) +} diff --git a/scim/scim.go b/scim/scim.go index cb3f01af0c..bd1bbc2011 100644 --- a/scim/scim.go +++ b/scim/scim.go @@ -120,7 +120,7 @@ type GroupList struct { StartIndex int32 `json:"startIndex,omitempty"` ItemsPerPage int32 `json:"itemsPerPage,omitempty"` Schemas []URN `json:"schemas,omitempty"` - Resources []Group `json:"resources,omitempty"` + Resources []Group `json:"Resources,omitempty"` } type email struct {