From 43ead70428e201ee3d6337e0af41cf3561dad00f Mon Sep 17 00:00:00 2001 From: Miles Yucht Date: Tue, 16 Apr 2024 10:31:22 +0200 Subject: [PATCH] Fix integration tests for `databricks_entitlements` (#3467) * work * More work --- .gitignore | 5 +- .../acceptance/entitlements_base_resources.go | 221 ++++++++++++++++ internal/acceptance/entitlements_test.go | 250 ++++++------------ 3 files changed, 313 insertions(+), 163 deletions(-) create mode 100644 internal/acceptance/entitlements_base_resources.go diff --git a/.gitignore b/.gitignore index 87d1af1516..a55b19c202 100644 --- a/.gitignore +++ b/.gitignore @@ -342,4 +342,7 @@ scripts/tt .metals -provider/completeness.md \ No newline at end of file +provider/completeness.md + +# VS Code debugging binaries +__debug_bin* diff --git a/internal/acceptance/entitlements_base_resources.go b/internal/acceptance/entitlements_base_resources.go new file mode 100644 index 0000000000..9c3a7ad227 --- /dev/null +++ b/internal/acceptance/entitlements_base_resources.go @@ -0,0 +1,221 @@ +package acceptance + +import ( + "context" + "fmt" + + "github.com/databricks/databricks-sdk-go" + "github.com/databricks/databricks-sdk-go/httpclient" + "github.com/databricks/databricks-sdk-go/service/iam" +) + +type entitlementResource interface { + resourceType() string + setDisplayName(string) + setWorkspaceClient(*databricks.WorkspaceClient) + create(context.Context) error + getEntitlements(context.Context) ([]iam.ComplexValue, error) + cleanUp(context.Context) error + dataSourceTemplate() string + tfReference() string +} + +type userResource struct { + email string + id string + w *databricks.WorkspaceClient +} + +func (u *userResource) resourceType() string { + return "user" +} + +func (u *userResource) setDisplayName(displayName string) { + u.email = displayName + "@example.com" +} + +func (u *userResource) setWorkspaceClient(w *databricks.WorkspaceClient) { + u.w = w +} + +func (u *userResource) create(ctx context.Context) error { + user, err := u.w.Users.Create(ctx, iam.User{ + UserName: u.email, + }) + if err != nil { + return err + } + u.id = user.Id + return nil +} + +func (u *userResource) getEntitlements(ctx context.Context) ([]iam.ComplexValue, error) { + c, err := u.w.Config.NewApiClient() + if err != nil { + return nil, err + } + res := iam.User{} + err = c.Do(ctx, "GET", fmt.Sprintf("/api/2.0/preview/scim/v2/Users/%s?attributes=entitlements", u.id), + httpclient.WithResponseUnmarshal(&res)) + if err != nil { + return nil, err + } + return res.Entitlements, nil +} + +func (u *userResource) cleanUp(ctx context.Context) error { + return u.w.Users.DeleteById(ctx, u.id) +} + +func (u *userResource) dataSourceTemplate() string { + return fmt.Sprintf(` + data "databricks_user" "example" { + user_name = "%s" + }`, u.email) +} + +func (u *userResource) tfReference() string { + return "user_id = data.databricks_user.example.id" +} + +type groupResource struct { + displayName string + id string + w *databricks.WorkspaceClient +} + +func (g *groupResource) resourceType() string { + return "group" +} + +func (g *groupResource) setDisplayName(displayName string) { + g.displayName = displayName +} + +func (g *groupResource) setWorkspaceClient(w *databricks.WorkspaceClient) { + g.w = w +} + +func (g *groupResource) create(ctx context.Context) error { + group, err := g.w.Groups.Create(ctx, iam.Group{ + DisplayName: g.displayName, + }) + if err != nil { + return err + } + g.id = group.Id + return nil +} + +func (g *groupResource) getEntitlements(ctx context.Context) ([]iam.ComplexValue, error) { + c, err := g.w.Config.NewApiClient() + if err != nil { + return nil, err + } + res := iam.Group{} + err = c.Do(ctx, "GET", fmt.Sprintf("/api/2.0/preview/scim/v2/Groups/%s?attributes=entitlements", g.id), + httpclient.WithResponseUnmarshal(&res)) + if err != nil { + return nil, err + } + return res.Entitlements, nil +} + +func (g *groupResource) cleanUp(ctx context.Context) error { + return g.w.Groups.DeleteById(ctx, g.id) +} + +func (g *groupResource) dataSourceTemplate() string { + return fmt.Sprintf(` + data "databricks_group" "example" { + display_name = "%s" + }`, g.displayName) +} + +func (g *groupResource) tfReference() string { + return "group_id = data.databricks_group.example.id" +} + +type servicePrincipalResource struct { + applicationId string + cleanup bool + displayName string + id string + w *databricks.WorkspaceClient +} + +func (s *servicePrincipalResource) resourceType() string { + return "service_principal" +} + +func (s *servicePrincipalResource) setDisplayName(displayName string) { + s.displayName = displayName +} + +func (s *servicePrincipalResource) setWorkspaceClient(w *databricks.WorkspaceClient) { + s.w = w +} + +func (s *servicePrincipalResource) create(ctx context.Context) error { + sp, err := s.create0(ctx) + if err != nil { + return err + } + if s.applicationId == "" { + s.applicationId = sp.ApplicationId + } + s.id = sp.Id + return nil +} + +func (s *servicePrincipalResource) create0(ctx context.Context) (iam.ServicePrincipal, error) { + if s.applicationId != "" { + sps := s.w.ServicePrincipals.List(ctx, iam.ListServicePrincipalsRequest{ + Filter: fmt.Sprintf(`applicationId eq "%s"`, s.applicationId), + }) + if !sps.HasNext(ctx) { + return iam.ServicePrincipal{}, fmt.Errorf("service principal with applicationId %s not found", s.applicationId) + } + return sps.Next(ctx) + } + sp, err := s.w.ServicePrincipals.Create(ctx, iam.ServicePrincipal{ + DisplayName: s.displayName, + }) + return *sp, err +} + +func (s *servicePrincipalResource) getEntitlements(ctx context.Context) ([]iam.ComplexValue, error) { + c, err := s.w.Config.NewApiClient() + if err != nil { + return nil, err + } + res := iam.ServicePrincipal{} + err = c.Do(ctx, "GET", fmt.Sprintf("/api/2.0/preview/scim/v2/ServicePrincipals/%s?attributes=entitlements", s.id), + httpclient.WithResponseUnmarshal(&res)) + if err != nil { + return nil, err + } + return res.Entitlements, nil +} + +func (s *servicePrincipalResource) cleanUp(ctx context.Context) error { + if !s.cleanup { + return nil + } + return s.w.ServicePrincipals.DeleteById(ctx, s.applicationId) +} + +func (s *servicePrincipalResource) dataSourceTemplate() string { + fragment := fmt.Sprintf(`display_name = "%s"`, s.displayName) + if s.applicationId != "" { + fragment = fmt.Sprintf(`application_id = "%s"`, s.applicationId) + } + return fmt.Sprintf(` + data "databricks_service_principal" "example" { + %s + }`, fragment) +} + +func (s *servicePrincipalResource) tfReference() string { + return "service_principal_id = data.databricks_service_principal.example.id" +} diff --git a/internal/acceptance/entitlements_test.go b/internal/acceptance/entitlements_test.go index d75b8f2792..7ab2414b23 100644 --- a/internal/acceptance/entitlements_test.go +++ b/internal/acceptance/entitlements_test.go @@ -7,103 +7,11 @@ import ( "testing" "github.com/databricks/databricks-sdk-go" - "github.com/databricks/databricks-sdk-go/httpclient" - "github.com/databricks/databricks-sdk-go/service/iam" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/databricks/databricks-sdk-go/logger" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/stretchr/testify/assert" ) -func TestAccEntitlementResource(t *testing.T) { - var conf = ` - resource "databricks_user" "first" { - user_name = "tf-eerste+{var.RANDOM}@example.com" - display_name = "Eerste {var.RANDOM}" - allow_cluster_create = true - allow_instance_pool_create = true - } - - resource "databricks_group" "second" { - display_name = "{var.RANDOM} group" - allow_cluster_create = true - allow_instance_pool_create = true - } - - resource "databricks_group" "third" { - display_name = "{var.RANDOM} group 2" - } - - resource "databricks_entitlements" "first_entitlements" { - user_id = databricks_user.first.id - allow_cluster_create = true - allow_instance_pool_create = true - } - - resource "databricks_entitlements" "second_entitlements" { - group_id = databricks_group.second.id - allow_cluster_create = true - allow_instance_pool_create = true - } - - resource "databricks_entitlements" "third_entitlements" { - group_id = databricks_group.third.id - allow_cluster_create = false - allow_instance_pool_create = false - databricks_sql_access = false - workspace_access = false - }` - workspaceLevel(t, step{ - Template: conf, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("databricks_entitlements.first_entitlements", "allow_cluster_create", "true"), - resource.TestCheckResourceAttr("databricks_entitlements.first_entitlements", "allow_instance_pool_create", "true"), - resource.TestCheckResourceAttr("databricks_entitlements.second_entitlements", "allow_cluster_create", "true"), - resource.TestCheckResourceAttr("databricks_entitlements.second_entitlements", "allow_instance_pool_create", "true"), - ), - }, step{ - Template: conf, - }) -} - -func TestAccServicePrincipalEntitlementsResourceOnAzure(t *testing.T) { - // this test should run only on Azure, so just expect SPN config to be there or fail - // TODO: change to SDK so that we can be explicit if we want to fetch entitlements - GetEnvOrSkipTest(t, "ARM_CLIENT_ID") - workspaceLevel(t, step{ - Template: `resource "databricks_service_principal" "this" { - application_id = "{var.RANDOM_UUID}" - allow_cluster_create = true - allow_instance_pool_create = true - display_name = "SPN {var.RANDOM}" - force = true - } - - resource "databricks_entitlements" "service_principal" { - service_principal_id = databricks_service_principal.this.id - allow_cluster_create = true - allow_instance_pool_create = true - }`, - }) -} - -func TestAccServicePrincipalEntitlementsResourceOnAws(t *testing.T) { - GetEnvOrSkipTest(t, "TEST_EC2_INSTANCE_PROFILE") - workspaceLevel(t, step{ - Template: ` - resource "databricks_service_principal" "this" { - display_name = "SPN {var.RANDOM}" - allow_cluster_create = true - allow_instance_pool_create = true - } - - resource "databricks_entitlements" "service_principal" { - service_principal_id = databricks_service_principal.this.id - allow_cluster_create = true - allow_instance_pool_create = true - }`, - }) -} - type entitlement struct { name string value bool @@ -113,7 +21,7 @@ func (e entitlement) String() string { return fmt.Sprintf("%s = %t", e.name, e.value) } -func entitlementsStepBuilder(t *testing.T, c **httpclient.ApiClient, groupName string) func(entitlements []entitlement) step { +func entitlementsStepBuilder(t *testing.T, r entitlementResource) func(entitlements []entitlement) step { return func(entitlements []entitlement) step { entitlementsBuf := strings.Builder{} for _, entitlement := range entitlements { @@ -121,24 +29,17 @@ func entitlementsStepBuilder(t *testing.T, c **httpclient.ApiClient, groupName s } return step{ Template: fmt.Sprintf(` - data "databricks_group" "example" { - display_name = "%s" - } - + %s resource "databricks_entitlements" "entitlements_users" { - group_id = data.databricks_group.example.id + %s %s } - `, groupName, entitlementsBuf.String()), + `, r.dataSourceTemplate(), r.tfReference(), entitlementsBuf.String()), Check: func(s *terraform.State) error { - groupId := s.RootModule().Resources["data.databricks_group.example"].Primary.ID - var res iam.Group - ctx := context.Background() - err := (*c).Do(ctx, "GET", fmt.Sprintf("/api/2.0/preview/scim/v2/Groups/%s?attributes=entitlements", groupId), - httpclient.WithResponseUnmarshal(&res)) + remoteEntitlements, err := r.getEntitlements(context.Background()) assert.NoError(t, err) - receivedEntitlements := make([]string, 0, len(res.Entitlements)) - for _, entitlement := range res.Entitlements { + receivedEntitlements := make([]string, 0, len(remoteEntitlements)) + for _, entitlement := range remoteEntitlements { receivedEntitlements = append(receivedEntitlements, entitlement.Value) } expectedEntitlements := make([]string, 0, len(entitlements)) @@ -155,89 +56,114 @@ func entitlementsStepBuilder(t *testing.T, c **httpclient.ApiClient, groupName s } } -func makeEntitlementsSteps(t *testing.T, entitlementsSteps [][]entitlement) []step { - groupName := RandomName("entitlements-") - var c *httpclient.ApiClient - makeEntitlementsStep := entitlementsStepBuilder(t, &c, groupName) +func makeEntitlementsSteps(t *testing.T, r entitlementResource, entitlementsSteps [][]entitlement) []step { + r.setDisplayName(RandomName("entitlements-")) + makeEntitlementsStep := entitlementsStepBuilder(t, r) steps := make([]step, len(entitlementsSteps)) for i, entitlements := range entitlementsSteps { steps[i] = makeEntitlementsStep(entitlements) } - steps[0].PreConfig = makePreconfig(t, &c, groupName) + steps[0].PreConfig = makePreconfig(t, r) return steps } -func makePreconfig(t *testing.T, c **httpclient.ApiClient, groupName string) func() { +func makePreconfig(t *testing.T, r entitlementResource) func() { + logger.DefaultLogger = &logger.SimpleLogger{ + Level: logger.LevelDebug, + } return func() { w := databricks.Must(databricks.NewWorkspaceClient()) - var err error - *c, err = w.Config.NewApiClient() - assert.NoError(t, err) + r.setWorkspaceClient(w) ctx := context.Background() - group, err := w.Groups.Create(ctx, iam.Group{ - DisplayName: groupName, - }) + err := r.create(ctx) assert.NoError(t, err) t.Cleanup(func() { - err := w.Groups.DeleteById(ctx, group.Id) - assert.NoError(t, err) + r.cleanUp(ctx) + }) + } +} + +func entitlementsTest(t *testing.T, f func(*testing.T, entitlementResource)) { + loadWorkspaceEnv(t) + sp := &servicePrincipalResource{} + if isAzure(t) { + // A long-lived application is used in Azure. + sp.applicationId = GetEnvOrSkipTest(t, "ACCOUNT_LEVEL_SERVICE_PRINCIPAL_ID") + sp.cleanup = false + } + resources := []entitlementResource{ + &groupResource{}, + &userResource{}, + sp, + } + for _, r := range resources { + t.Run(r.resourceType(), func(t *testing.T) { + f(t, r) }) } } func TestAccEntitlementsAddToEmpty(t *testing.T) { - steps := makeEntitlementsSteps(t, [][]entitlement{ - {}, - { - {"allow_cluster_create", true}, - {"allow_instance_pool_create", true}, - {"workspace_access", true}, - {"databricks_sql_access", true}, - }, + entitlementsTest(t, func(t *testing.T, r entitlementResource) { + steps := makeEntitlementsSteps(t, r, [][]entitlement{ + {}, + { + {"allow_cluster_create", true}, + {"allow_instance_pool_create", true}, + {"workspace_access", true}, + {"databricks_sql_access", true}, + }, + }) + workspaceLevel(t, steps...) }) - workspaceLevel(t, steps...) } func TestAccEntitlementsSetExplicitlyToFalse(t *testing.T) { - steps := makeEntitlementsSteps(t, [][]entitlement{ - { - {"allow_cluster_create", false}, - {"allow_instance_pool_create", false}, - {"workspace_access", false}, - {"databricks_sql_access", false}, - }, - {}, - { - {"allow_cluster_create", false}, - {"allow_instance_pool_create", false}, - {"workspace_access", false}, - {"databricks_sql_access", false}, - }, + entitlementsTest(t, func(t *testing.T, r entitlementResource) { + steps := makeEntitlementsSteps(t, r, [][]entitlement{ + { + {"allow_cluster_create", false}, + {"allow_instance_pool_create", false}, + {"workspace_access", false}, + {"databricks_sql_access", false}, + }, + {}, + { + {"allow_cluster_create", false}, + {"allow_instance_pool_create", false}, + {"workspace_access", false}, + {"databricks_sql_access", false}, + }, + }) + workspaceLevel(t, steps...) }) - workspaceLevel(t, steps...) } func TestAccEntitlementsRemoveExisting(t *testing.T) { - steps := makeEntitlementsSteps(t, [][]entitlement{ - { - {"allow_cluster_create", true}, - {"allow_instance_pool_create", true}, - {"workspace_access", true}, - {"databricks_sql_access", true}, - }, - {}, + entitlementsTest(t, func(t *testing.T, r entitlementResource) { + steps := makeEntitlementsSteps(t, r, [][]entitlement{ + { + {"allow_cluster_create", true}, + {"allow_instance_pool_create", true}, + {"workspace_access", true}, + {"databricks_sql_access", true}, + }, + {}, + }) + workspaceLevel(t, steps...) }) - workspaceLevel(t, steps...) } func TestAccEntitlementsSomeTrueSomeFalse(t *testing.T) { - steps := makeEntitlementsSteps(t, [][]entitlement{ - { - {"allow_cluster_create", false}, - {"allow_instance_pool_create", false}, - {"workspace_access", true}, - {"databricks_sql_access", true}, - }, + entitlementsTest(t, func(t *testing.T, r entitlementResource) { + steps := makeEntitlementsSteps(t, r, [][]entitlement{ + { + {"allow_cluster_create", false}, + {"allow_instance_pool_create", false}, + {"workspace_access", true}, + {"databricks_sql_access", true}, + }, + }) + workspaceLevel(t, steps...) }) - workspaceLevel(t, steps...) }