Skip to content

Commit

Permalink
Add revert functionality (#334)
Browse files Browse the repository at this point in the history
* Inital solution for revert changes after failTeamConnectorMembership

* Implement rest of resources

* Add tests and logs

* Fix tests

* Fix typo and add log for failed reverts
  • Loading branch information
fivetran-jovanmanojlovic authored Aug 14, 2024
1 parent 04be93a commit a5dadb6
Show file tree
Hide file tree
Showing 10 changed files with 1,245 additions and 219 deletions.
475 changes: 291 additions & 184 deletions fivetran/framework/resources/team_connector_membership.go

Large diffs are not rendered by default.

113 changes: 108 additions & 5 deletions fivetran/framework/resources/team_connector_membership_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package resources_test

import (
"fmt"
"net/http"
"regexp"
"testing"

tfmock "github.com/fivetran/terraform-provider-fivetran/fivetran/tests/mock"

"github.com/fivetran/go-fivetran/tests/mock"
tfmock "github.com/fivetran/terraform-provider-fivetran/fivetran/tests/mock"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
)
Expand All @@ -14,9 +16,11 @@ var (
teamConnectorMembershipPostHandler *mock.Handler
teamConnectorMembershipPatchHandler *mock.Handler
teamConnectorMembershipDeleteHandler *mock.Handler
teamConnectorMembershipData map[string]interface{}
teamConnectorMembershipListData map[string]interface{}
teamConnectorMembershipResponse string
teamConnectorMembershipData map[string]interface{}
teamConnectorMembershipListData map[string]interface{}
teamConnectorMembershipResponse string
teamConnectorMembershipResponse2 string
teamConnectormembershipDeleteCount int
)

func setupMockClientTeamConnectorMembershipResource(t *testing.T) {
Expand Down Expand Up @@ -120,3 +124,102 @@ func TestConnectorMembershipResourceTeamMock(t *testing.T) {
},
)
}

func setupMockClientTeamConnectorMembershipResourceNotFound(t *testing.T) {
tfmock.MockClient().Reset()
teamConnectorMembershipResponse =
`{
"id": "test_connector",
"role": "Connector Reviewer",
"created_at": "2020-05-25T15:26:47.306509Z"
}`
teamConnectorMembershipResponse2 =
`{
"id": "test_connector",
"role": "Connector Reviewer",
"created_at": "2020-05-25T15:26:47.306509Z"
}`

teamConnectorMembershipResponse = `{
"items": [
{
"id": "test_connector",
"role": "Connector Reviewer",
"created_at": "2020-05-25T15:26:47.306509Z"
}
],
"next_cursor": null}`

callCount := 0
teamConnectorMembershipPostHandler = tfmock.MockClient().When(http.MethodPost, "/v1/teams/test_team/connectors").ThenCall(
func(req *http.Request) (*http.Response, error) {
callCount++
if callCount != 1 {
teamConnectorMembershipData = tfmock.CreateMapFromJsonString(t, teamConnectorMembershipResponse)
return tfmock.FivetranSuccessResponse(t, req, http.StatusCreated, "Connector membership has been created", teamConnectorMembershipData), nil
}
teamConnectorMembershipData = tfmock.CreateMapFromJsonString(t, teamConnectorMembershipResponse)
return tfmock.FivetranSuccessResponse(t, req, http.StatusNotFound, "Connector membership not found", teamConnectorMembershipData), nil
},
)

teamConnectormembershipDeleteCount := 0;
teamConnectorMembershipDeleteHandler = tfmock.MockClient().When(http.MethodDelete, "/v1/teams/test_team/connectors/test_connector").ThenCall(
func(req *http.Request) (*http.Response, error) {
teamConnectormembershipDeleteCount++
return tfmock.FivetranSuccessResponse(t, req, 200, "Connector membership has been deleted", nil), nil
},
)

tfmock.MockClient().When(http.MethodGet, "/v1/teams/test_team/connectors").ThenCall(
func(req *http.Request) (*http.Response, error) {
teamConnectorMembershipListData = tfmock.CreateMapFromJsonString(t, teamConnectorMembershipResponse)
response := tfmock.FivetranSuccessResponse(t, req, http.StatusOK, "", teamConnectorMembershipListData)
return response, nil
},
)

}

func TestConnectorMembershipResourceTeamMockNotFound(t *testing.T) {
step1 := resource.TestStep{
Config: `
resource "fivetran_team_connector_membership" "test_team_connector_membership" {
provider = fivetran-provider
team_id = "test_team"
connector {
connector_id = "test_connector"
role = "Connector Reviewer"
}
connector {
connector_id = "test_connector2"
role = "Connector Reviewer"
}
}`,
ExpectError: regexp.MustCompile(`Error: Unable to Create Team Connector Memberships Resource`),
}

resource.Test(
t,
resource.TestCase{
PreCheck: func() {
setupMockClientTeamConnectorMembershipResourceNotFound(t)
},
ProtoV6ProviderFactories: tfmock.ProtoV6ProviderFactories,
CheckDestroy: func(s *terraform.State) error {
tfmock.AssertEqual(t, teamConnectorMembershipDeleteHandler.Interactions, 1)
if (teamConnectormembershipDeleteCount != 1) {
return fmt.Errorf("Failed acctions are not reverted.")
}
return nil
},

Steps: []resource.TestStep{
step1,
},
},
)
}
114 changes: 109 additions & 5 deletions fivetran/framework/resources/team_group_membership.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,27 @@ func (r *teamGroupMembership) Create(ctx context.Context, req resource.CreateReq

resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)

savedGroups := make([]string, 0, len(data.Group.Elements()))
for _, group := range data.Group.Elements() {
if groupElement, ok := group.(basetypes.ObjectValue); ok {
groupId := groupElement.Attributes()["group_id"].(basetypes.StringValue).ValueString()
svc := r.GetClient().NewTeamGroupMembershipCreate()
svc.TeamId(data.TeamId.ValueString())
svc.GroupId(groupElement.Attributes()["group_id"].(basetypes.StringValue).ValueString())
svc.GroupId(groupId)
svc.Role(groupElement.Attributes()["role"].(basetypes.StringValue).ValueString())
if teamGroupResponse, err := svc.Do(ctx); err != nil {
resp.Diagnostics.AddError(
"Unable to Create Team Group Memberships Resource.",
fmt.Sprintf("%v; code: %v; message: %v", err, teamGroupResponse.Code, teamGroupResponse.Message),
)


creRvertMsg, creRevertErr := r.RevertCreated(ctx, savedGroups, data.TeamId.ValueString())
resp.Diagnostics.AddWarning("Action reverted", creRvertMsg)
resp.Diagnostics.AddWarning("Action reverted failed", creRevertErr)
return
}
savedGroups = append(savedGroups, groupId)
}
}

Expand Down Expand Up @@ -140,6 +147,8 @@ func (r *teamGroupMembership) Update(ctx context.Context, req resource.UpdateReq
}

/* sync */
deletedGroups := make([]string, 0)
modifiedGroups := make([]string, 0)
for stateKey, stateValue := range stateGroupsMap {
role, found := planGroupsMap[stateKey]

Expand All @@ -149,19 +158,34 @@ func (r *teamGroupMembership) Update(ctx context.Context, req resource.UpdateReq
"Unable to Update Team Group Membership Resource.",
fmt.Sprintf("%v; code: %v; message: %v", err, updateResponse.Code, updateResponse.Message),
)

delRevertMsg, delRevertErr := r.RevertDeleted(ctx, deletedGroups, plan.TeamId.ValueString(), stateGroupsMap)
resp.Diagnostics.AddWarning("Action reverted", delRevertMsg)
resp.Diagnostics.AddWarning("Action reverted failed", delRevertErr)
return
}
deletedGroups = append(deletedGroups, stateKey)
} else if role != stateValue {
if updateResponse, err := r.GetClient().NewTeamGroupMembershipModify().TeamId(plan.TeamId.ValueString()).GroupId(stateKey).Role(role).Do(ctx); err != nil {
resp.Diagnostics.AddError(
"Unable to Update Team Group Membership Resource.",
fmt.Sprintf("%v; code: %v; message: %v", err, updateResponse.Code, updateResponse.Message),
)

delRevertMsg, delRevertErr := r.RevertDeleted(ctx, deletedGroups, plan.TeamId.ValueString(), stateGroupsMap)
resp.Diagnostics.AddWarning("Action reverted", delRevertMsg)
resp.Diagnostics.AddWarning("Action reverted failed", delRevertErr)

modRevertMsg, modRevertErr := r.RevertModified(ctx, modifiedGroups, plan.TeamId.ValueString(), stateGroupsMap)
resp.Diagnostics.AddWarning("Action reverted", modRevertMsg)
resp.Diagnostics.AddWarning("Action reverted failed", modRevertErr)
return
}
modifiedGroups = append(modifiedGroups, stateKey)
}
}

createdGroups := make([]string, 0)
for planKey, planValue := range planGroupsMap {
_, exists := stateGroupsMap[planKey]

Expand All @@ -171,8 +195,22 @@ func (r *teamGroupMembership) Update(ctx context.Context, req resource.UpdateReq
"Unable to Update Team Group Membership Resource.",
fmt.Sprintf("%v; code: %v; message: %v", err, updateResponse.Code, updateResponse.Message),
)

delRevertMsg, delRevertErr := r.RevertDeleted(ctx, deletedGroups, plan.TeamId.ValueString(), stateGroupsMap)
resp.Diagnostics.AddWarning("Action reverted", delRevertMsg)
resp.Diagnostics.AddWarning("Action reverted failed", delRevertErr)

modRevertMsg, modRevertErr := r.RevertModified(ctx, modifiedGroups, plan.TeamId.ValueString(), stateGroupsMap)
resp.Diagnostics.AddWarning("Action reverted", modRevertMsg)
resp.Diagnostics.AddWarning("Action reverted failed", modRevertErr)

creRvertMsg, creRevertErr := r.RevertCreated(ctx, createdGroups, plan.TeamId.ValueString())
resp.Diagnostics.AddWarning("Action reverted", creRvertMsg)
resp.Diagnostics.AddWarning("Action reverted failed", creRevertErr)

return
}
createdGroups = append(createdGroups, planKey)
}
}

Expand All @@ -182,7 +220,6 @@ func (r *teamGroupMembership) Update(ctx context.Context, req resource.UpdateReq
"Unable to Create Team Group Memberships Resource.",
fmt.Sprintf("%v; code: %v", err, teamGroupResponse.Code),
)

return
}

Expand All @@ -201,24 +238,91 @@ func (r *teamGroupMembership) Delete(ctx context.Context, req resource.DeleteReq
return
}

var data model.TeamGroupMemberships
var data, state model.TeamGroupMemberships

resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)

stateGroupsMap := make(map[string]string)
for _, group := range state.Group.Elements() {
if groupElement, ok := group.(basetypes.ObjectValue); ok {
stateGroupsMap[groupElement.Attributes()["group_id"].(basetypes.StringValue).ValueString()] = groupElement.Attributes()["role"].(basetypes.StringValue).ValueString()
}
}

deletedGroups := make([]string, 0)
for _, group := range data.Group.Elements() {
if groupElement, ok := group.(basetypes.ObjectValue); ok {
groupId := groupElement.Attributes()["group_id"].(basetypes.StringValue).ValueString()
svc := r.GetClient().NewTeamGroupMembershipDelete()
svc.TeamId(data.TeamId.ValueString())
svc.GroupId(groupElement.Attributes()["group_id"].(basetypes.StringValue).ValueString())
svc.GroupId(groupId)

if deleteResponse, err := svc.Do(ctx); err != nil {
resp.Diagnostics.AddError(
"Unable to Delete Team Group Memberships Resource.",
fmt.Sprintf("%v; code: %v; message: %v", err, deleteResponse.Code, deleteResponse.Message),
)

delRevertMsg, delRevertErr := r.RevertDeleted(ctx, deletedGroups, data.TeamId.ValueString(), stateGroupsMap);
resp.Diagnostics.AddWarning("Action reverted", delRevertMsg)
resp.Diagnostics.AddWarning("Action reverted failed", delRevertErr)
return
}
deletedGroups = append(deletedGroups, groupId)
}
}
}
}

func (r *teamGroupMembership) RevertDeleted(ctx context.Context, toRevert []string, teamId string, stateGroupsMap map[string]string) (string, string) {
reverted := []string{}
failed := []string{}
for _, groupId := range toRevert {
svc := r.GetClient().NewTeamGroupMembershipCreate()
svc.TeamId(teamId)
svc.GroupId(groupId)
svc.Role(stateGroupsMap[groupId])
if _, err := svc.Do(ctx); err != nil {
failed = append(failed, groupId)
} else {
reverted = append(reverted, groupId)
}
}
return fmt.Sprintf("Delete action reverted for groups: %v", reverted),
fmt.Sprintf("Delete for revert action failed for groups: %v", failed)
}

func (r *teamGroupMembership) RevertModified(ctx context.Context, toRevert []string, teamId string, stateGroupsMap map[string]string) (string, string) {
reverted := []string{}
failed := []string{}
for _, groupId := range toRevert {
svc := r.GetClient().NewTeamGroupMembershipModify()
svc.TeamId(teamId)
svc.GroupId(groupId)
svc.Role(stateGroupsMap[groupId])
if _, err := svc.Do(ctx); err != nil {
failed = append(failed, groupId)
} else {
reverted = append(reverted, groupId)
}
}
return fmt.Sprintf("Modify action reverted for groups: %v", reverted),
fmt.Sprintf("Modify for revert action failed for groups: %v", failed)
}

func (r *teamGroupMembership) RevertCreated(ctx context.Context, toRevert []string, teamId string)(string, string) {
reverted := []string{}
failed := []string{}
for _, groupId := range toRevert {
svc := r.GetClient().NewTeamGroupMembershipDelete()
svc.TeamId(teamId)
svc.GroupId(groupId)
if _, err := svc.Do(ctx); err != nil {
failed = append(failed, groupId)
} else {
reverted = append(reverted, groupId)
}
}
return fmt.Sprintf("Create action reverted for groups: %v", reverted),
fmt.Sprintf("Create for revert action failed for groups: %v", failed)
}
Loading

0 comments on commit a5dadb6

Please sign in to comment.