Skip to content

Commit

Permalink
feat: Create / Read / Delete implementation and tests for `mongodbatl…
Browse files Browse the repository at this point in the history
…as_employee_access_grant` (#2573)

* main_test file

* CRU operations

* unit tests

* basic acc test

* basic mig test

* more acc tests

* adding expectedErrContains as per PR feedback

* check err in test
  • Loading branch information
lantoli authored Sep 10, 2024
1 parent 57dcfe8 commit 7b92a09
Show file tree
Hide file tree
Showing 8 changed files with 328 additions and 8 deletions.
2 changes: 1 addition & 1 deletion internal/service/employeeaccessgrant/data_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ var _ datasource.DataSourceWithConfigure = &employeeAccessGrantDS{}
func DataSource() datasource.DataSource {
return &employeeAccessGrantDS{
DSCommon: config.DSCommon{
DataSourceName: employeeAccessGrantName,
DataSourceName: resourceName,
},
}
}
Expand Down
15 changes: 15 additions & 0 deletions internal/service/employeeaccessgrant/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package employeeaccessgrant_test

import (
"os"
"testing"

"github.com/mongodb/terraform-provider-mongodbatlas/internal/testutil/acc"
)

func TestMain(m *testing.M) {
cleanup := acc.SetupSharedResources()
exitCode := m.Run()
cleanup()
os.Exit(exitCode)
}
30 changes: 30 additions & 0 deletions internal/service/employeeaccessgrant/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package employeeaccessgrant

import (
"fmt"

"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/conversion"
"go.mongodb.org/atlas-sdk/v20240805003/admin"
)

func NewTFModel(projectID, clusterName string, apiResp *admin.EmployeeAccessGrant) *TFModel {
return &TFModel{
ProjectID: types.StringValue(projectID),
ClusterName: types.StringValue(clusterName),
GrantType: types.StringValue(apiResp.GetGrantType()),
ExpirationTime: types.StringValue(conversion.TimeToString(apiResp.GetExpirationTime())),
}
}

func NewAtlasReq(tfModel *TFModel) (*admin.EmployeeAccessGrant, error) {
expirationTimeStr := tfModel.ExpirationTime.ValueString()
expirationTime, ok := conversion.StringToTime(expirationTimeStr)
if !ok {
return nil, fmt.Errorf("expiration_time format is incorrect: %s", expirationTimeStr)
}
return &admin.EmployeeAccessGrant{
GrantType: tfModel.GrantType.ValueString(),
ExpirationTime: expirationTime,
}, nil
}
82 changes: 82 additions & 0 deletions internal/service/employeeaccessgrant/model_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package employeeaccessgrant_test

import (
"testing"
"time"

"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/mongodb/terraform-provider-mongodbatlas/internal/service/employeeaccessgrant"
"github.com/stretchr/testify/assert"
"go.mongodb.org/atlas-sdk/v20240805003/admin"
)

func TestNewTFModel(t *testing.T) {
testCases := map[string]struct {
apiResp *admin.EmployeeAccessGrant
expectedTFModel *employeeaccessgrant.TFModel
projectID string
clusterName string
}{
"valid": {
projectID: "projectID",
clusterName: "clusterName",
apiResp: &admin.EmployeeAccessGrant{
GrantType: "grantType",
ExpirationTime: time.Date(2024, 10, 13, 0, 0, 0, 0, time.UTC),
},
expectedTFModel: &employeeaccessgrant.TFModel{
ProjectID: types.StringValue("projectID"),
ClusterName: types.StringValue("clusterName"),
GrantType: types.StringValue("grantType"),
ExpirationTime: types.StringValue("2024-10-13T00:00:00Z"),
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
tfModel := employeeaccessgrant.NewTFModel(tc.projectID, tc.clusterName, tc.apiResp)
assert.Equal(t, tc.expectedTFModel, tfModel)
})
}
}

func TestNewAtlasReq(t *testing.T) {
testCases := map[string]struct {
tfModel *employeeaccessgrant.TFModel
expectedReq *admin.EmployeeAccessGrant
expectedErrContains string
}{
"valid": {
tfModel: &employeeaccessgrant.TFModel{
ProjectID: types.StringValue("projectID"),
ClusterName: types.StringValue("clusterName"),
GrantType: types.StringValue("grantType"),
ExpirationTime: types.StringValue("2024-10-13T00:00:00Z"),
},
expectedReq: &admin.EmployeeAccessGrant{
GrantType: "grantType",
ExpirationTime: time.Date(2024, 10, 13, 0, 0, 0, 0, time.UTC),
},
},
"invalid expiration time": {
tfModel: &employeeaccessgrant.TFModel{
ProjectID: types.StringValue("projectID"),
ClusterName: types.StringValue("clusterName"),
GrantType: types.StringValue("grantType"),
ExpirationTime: types.StringValue("invalid_time"),
},
expectedErrContains: "invalid_time",
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
req, err := employeeaccessgrant.NewAtlasReq(tc.tfModel)
assert.Equal(t, tc.expectedErrContains == "", err == nil)
if err == nil {
assert.Equal(t, tc.expectedReq, req)
} else {
assert.Contains(t, err.Error(), tc.expectedErrContains)
}
})
}
}
67 changes: 61 additions & 6 deletions internal/service/employeeaccessgrant/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,27 @@ package employeeaccessgrant

import (
"context"
"net/http"

"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/mongodb/terraform-provider-mongodbatlas/internal/config"
)

const employeeAccessGrantName = "employee_access_grant"
const (
resourceName = "employee_access_grant"
fullResourceName = "mongodbatlas_" + resourceName
errorCreate = "Error creating resource " + fullResourceName
errorRead = "Error retrieving info for resource " + fullResourceName
errorDelete = "Error deleting resource " + fullResourceName
)

var _ resource.ResourceWithConfigure = &employeeAccessGrantRS{}
var _ resource.ResourceWithImportState = &employeeAccessGrantRS{}

func Resource() resource.Resource {
return &employeeAccessGrantRS{
RSCommon: config.RSCommon{
ResourceName: employeeAccessGrantName,
ResourceName: resourceName,
},
}
}
Expand All @@ -29,19 +36,54 @@ func (r *employeeAccessGrantRS) Schema(ctx context.Context, req resource.SchemaR
}

func (r *employeeAccessGrantRS) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var tfPlan TFEmployeeAccessGrantModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &tfPlan)...)
var tfModel TFModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &tfModel)...)
if resp.Diagnostics.HasError() {
return
}
resp.Diagnostics.Append(resp.State.Set(ctx, tfPlan)...)
atlasReq, err := NewAtlasReq(&tfModel)
if err != nil {
resp.Diagnostics.AddError(errorCreate, err.Error())
return
}
connV2 := r.Client.AtlasV2
projectID := tfModel.ProjectID.ValueString()
clusterName := tfModel.ClusterName.ValueString()
if _, _, err := connV2.ClustersApi.GrantMongoDBEmployeeAccess(ctx, projectID, clusterName, atlasReq).Execute(); err != nil {
resp.Diagnostics.AddError(errorCreate, err.Error())
return
}
resp.Diagnostics.Append(resp.State.Set(ctx, tfModel)...)
}

func (r *employeeAccessGrantRS) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var tfModel TFModel
resp.Diagnostics.Append(req.State.Get(ctx, &tfModel)...)
if resp.Diagnostics.HasError() {
return
}
connV2 := r.Client.AtlasV2
projectID := tfModel.ProjectID.ValueString()
clusterName := tfModel.ClusterName.ValueString()
cluster, httpResp, err := connV2.ClustersApi.GetCluster(ctx, projectID, clusterName).Execute()
if httpResp.StatusCode == http.StatusNotFound {
resp.State.RemoveResource(ctx)
return
}
if err != nil {
resp.Diagnostics.AddError(errorCreate, err.Error())
return
}
atlasResp, _ := cluster.GetMongoDBEmployeeAccessGrantOk()
if atlasResp == nil {
resp.State.RemoveResource(ctx)
return
}
resp.Diagnostics.Append(resp.State.Set(ctx, NewTFModel(projectID, clusterName, atlasResp))...)
}

func (r *employeeAccessGrantRS) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var tfPlan TFEmployeeAccessGrantModel
var tfPlan TFModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &tfPlan)...)
if resp.Diagnostics.HasError() {
return
Expand All @@ -50,6 +92,19 @@ func (r *employeeAccessGrantRS) Update(ctx context.Context, req resource.UpdateR
}

func (r *employeeAccessGrantRS) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var tfModel TFModel
resp.Diagnostics.Append(req.State.Get(ctx, &tfModel)...)
if resp.Diagnostics.HasError() {
return
}
connV2 := r.Client.AtlasV2
projectID := tfModel.ProjectID.ValueString()
clusterName := tfModel.ClusterName.ValueString()
_, httpResp, err := connV2.ClustersApi.RevokeMongoDBEmployeeAccess(ctx, projectID, clusterName).Execute()
if err != nil && httpResp.StatusCode != http.StatusNotFound {
resp.Diagnostics.AddError(errorDelete, err.Error())
return
}
}

func (r *employeeAccessGrantRS) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
Expand Down
12 changes: 12 additions & 0 deletions internal/service/employeeaccessgrant/resource_migration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package employeeaccessgrant_test

import (
"testing"

"github.com/mongodb/terraform-provider-mongodbatlas/internal/testutil/mig"
)

func TestMigEmployeeAccessGrant_basic(t *testing.T) {
mig.SkipIfVersionBelow(t, "1.20.0") // this feature was introduced in provider version 1.20.0
mig.CreateAndRunTestNonParallel(t, basicTestCase(t)) // does not run in parallel to reuse same execution project and cluster
}
2 changes: 1 addition & 1 deletion internal/service/employeeaccessgrant/resource_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func ResourceSchema(ctx context.Context) schema.Schema {
}
}

type TFEmployeeAccessGrantModel struct {
type TFModel struct {
ProjectID types.String `tfsdk:"project_id"`
ClusterName types.String `tfsdk:"cluster_name"`
GrantType types.String `tfsdk:"grant_type"`
Expand Down
126 changes: 126 additions & 0 deletions internal/service/employeeaccessgrant/resource_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package employeeaccessgrant_test

import (
"context"
"fmt"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"github.com/mongodb/terraform-provider-mongodbatlas/internal/testutil/acc"
)

const (
resourceName = "mongodbatlas_employee_access_grant.test"
dataSourceName = "data.mongodbatlas_employee_access_grant.test"
grantType = "CLUSTER_INFRASTRUCTURE"
expirationTime = "2025-08-01T12:00:00Z"
grantTypeInvalid = "invalid_grant_type"
expirationTimeInvalid = "invalid_time"
)

func TestAccEmployeeAccessGrant_basic(t *testing.T) {
resource.Test(t, *basicTestCase(t))
}

func basicTestCase(tb testing.TB) *resource.TestCase {
tb.Helper()
projectID, clusterName := acc.ClusterNameExecution(tb)
return &resource.TestCase{
PreCheck: func() { acc.PreCheckBasic(tb) },
ProtoV6ProviderFactories: acc.TestAccProviderV6Factories,
CheckDestroy: checkDestroy,
Steps: []resource.TestStep{
{
Config: configBasic(projectID, clusterName, grantType, expirationTime),
Check: checkBasic(projectID, clusterName, grantType, expirationTime),
},
},
}
}

func TestAccEmployeeAccessGrant_invalidExpirationTime(t *testing.T) {
projectID, clusterName := acc.ClusterNameExecution(t)
resource.Test(t, resource.TestCase{
PreCheck: func() { acc.PreCheckBasic(t) },
ProtoV6ProviderFactories: acc.TestAccProviderV6Factories,
CheckDestroy: checkDestroy,
Steps: []resource.TestStep{
{
Config: configBasic(projectID, clusterName, grantType, expirationTimeInvalid),
ExpectError: regexp.MustCompile("expiration_time format is incorrect.*" + expirationTimeInvalid),
},
},
})
}

func TestAccEmployeeAccessGrant_invalidGrantType(t *testing.T) {
projectID, clusterName := acc.ClusterNameExecution(t)
resource.Test(t, resource.TestCase{
PreCheck: func() { acc.PreCheckBasic(t) },
ProtoV6ProviderFactories: acc.TestAccProviderV6Factories,
CheckDestroy: checkDestroy,
Steps: []resource.TestStep{
{
Config: configBasic(projectID, clusterName, grantTypeInvalid, expirationTime),
ExpectError: regexp.MustCompile("invalid enumeration value.*" + grantTypeInvalid),
},
},
})
}

func configBasic(projectID, clusterName, grantType, expirationTime string) string {
return fmt.Sprintf(`
resource "mongodbatlas_employee_access_grant" "test" {
project_id = %[1]q
cluster_name = %[2]q
grant_type = %[3]q
expiration_time = %[4]q
}
`, projectID, clusterName, grantType, expirationTime)
}

func checkBasic(projectID, clusterName, grantType, expirationTime string) resource.TestCheckFunc {
checks := []resource.TestCheckFunc{checkExists(resourceName)}
attrsMap := map[string]string{
"project_id": projectID,
"cluster_name": clusterName,
"grant_type": grantType,
"expiration_time": expirationTime,
}
checks = acc.AddAttrChecks(resourceName, checks, attrsMap)
return resource.ComposeAggregateTestCheckFunc(checks...)
}

func checkExists(resourceName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resourceName]
if !ok {
return fmt.Errorf("not found: %s", resourceName)
}
if !exists(rs) {
return fmt.Errorf("employee access grant (%s) does not exist", resourceName)
}
return nil
}
}

func checkDestroy(state *terraform.State) error {
for _, rs := range state.RootModule().Resources {
if rs.Type == "mongodbatlas_employee_access_grant" {
if exists(rs) {
return fmt.Errorf("employee access grant still exists")
}
}
}
return nil
}

func exists(rs *terraform.ResourceState) bool {
projectID := rs.Primary.Attributes["project_id"]
clusterName := rs.Primary.Attributes["cluster_name"]
cluster, _, _ := acc.ConnV2().ClustersApi.GetCluster(context.Background(), projectID, clusterName).Execute()
resp, _ := cluster.GetMongoDBEmployeeAccessGrantOk()
return resp != nil
}

0 comments on commit 7b92a09

Please sign in to comment.