Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Organization Role assignments for Team #813

Merged
merged 4 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions client/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ type ApiClientInterface interface {
AssignTeamRoleToEnvironment(payload *AssignTeamRoleToEnvironmentPayload) (*TeamRoleEnvironmentAssignment, error)
RemoveTeamRoleFromEnvironment(environmentId string, teamId string) error
TeamRoleEnvironmentAssignments(environmentId string) ([]TeamRoleEnvironmentAssignment, error)
AssignOrganizationRoleToTeam(payload *AssignOrganizationRoleToTeamPayload) (*OrganizationRoleTeamAssignment, error)
RemoveOrganizationRoleFromTeam(teamId string) error
OrganizationRoleTeamAssignments() ([]OrganizationRoleTeamAssignment, error)
ApprovalPolicies(name string) ([]ApprovalPolicy, error)
ApprovalPolicyAssign(assignment *ApprovalPolicyAssignment) (*ApprovalPolicyAssignment, error)
ApprovalPolicyUnassign(scope string, scopeId string) error
Expand Down
44 changes: 44 additions & 0 deletions client/api_client_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions client/team_organization_assignment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package client

type AssignOrganizationRoleToTeamPayload struct {
TeamId string `json:"teamId"`
Role string `json:"role" tfschema:"role_id"`
}

type OrganizationRoleTeamAssignment struct {
TeamId string `json:"teamId"`
Role string `json:"role" tfschema:"role_id"`
Id string `json:"id"`
}

func (client *ApiClient) AssignOrganizationRoleToTeam(payload *AssignOrganizationRoleToTeamPayload) (*OrganizationRoleTeamAssignment, error) {
var result OrganizationRoleTeamAssignment

organizationId, err := client.OrganizationId()
if err != nil {
return nil, err
}

payloadWithOrganizationId := struct {
*AssignOrganizationRoleToTeamPayload
OrganizationId string `json:"organizationId"`
}{
payload,
organizationId,
}

if err := client.http.Put("/roles/assignments/teams", payloadWithOrganizationId, &result); err != nil {
return nil, err
}

return &result, nil
}

func (client *ApiClient) RemoveOrganizationRoleFromTeam(teamId string) error {
organizationId, err := client.OrganizationId()
if err != nil {
return err
}

return client.http.Delete("/roles/assignments/teams", map[string]string{"organizationId": organizationId, "teamId": teamId})
}

func (client *ApiClient) OrganizationRoleTeamAssignments() ([]OrganizationRoleTeamAssignment, error) {
organizationId, err := client.OrganizationId()
if err != nil {
return nil, err
}

var result []OrganizationRoleTeamAssignment

if err := client.http.Get("/roles/assignments/teams", map[string]string{"organizationId": organizationId}, &result); err != nil {
return nil, err
}

return result, nil
}
142 changes: 142 additions & 0 deletions client/team_organization_assignment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package client_test

import (
"errors"

. "github.com/env0/terraform-provider-env0/client"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"go.uber.org/mock/gomock"
)

var _ = Describe("Team Orgnization Assignment", func() {
organizationId := "organizationId"
teamId := "teamId"

assignPayload := &AssignOrganizationRoleToTeamPayload{
TeamId: teamId,
Role: "role1",
}

assignPayloadWithOrganizationId := struct {
*AssignOrganizationRoleToTeamPayload
OrganizationId string `json:"organizationId"`
}{
assignPayload,
organizationId,
}

expectedResponse := &OrganizationRoleTeamAssignment{
Id: "id",
}

errorMock := errors.New("error")

Describe("AssignTeamToOrganization", func() {

Describe("Successful", func() {
var actualResult *OrganizationRoleTeamAssignment
var err error

BeforeEach(func() {
mockOrganizationIdCall(organizationId).Times(1)
httpCall = mockHttpClient.EXPECT().
Put("/roles/assignments/teams", assignPayloadWithOrganizationId, gomock.Any()).
Do(func(path string, request interface{}, response *OrganizationRoleTeamAssignment) {
*response = *expectedResponse
}).Times(1)
actualResult, err = apiClient.AssignOrganizationRoleToTeam(assignPayload)

})

It("Should send POST request with params", func() {})

It("should return the PUT result", func() {
Expect(*actualResult).To(Equal(*expectedResponse))
})

It("Should not return error", func() {
Expect(err).To(BeNil())
})
})

Describe("Failure", func() {
var actualResult *OrganizationRoleTeamAssignment
var err error

BeforeEach(func() {
mockOrganizationIdCall(organizationId).Times(1)
httpCall = mockHttpClient.EXPECT().
Put("/roles/assignments/teams", gomock.Any(), gomock.Any()).Return(errorMock).Times(1)
actualResult, err = apiClient.AssignOrganizationRoleToTeam(assignPayload)
})

It("Should fail if API call fails", func() {
Expect(err).To(Equal(errorMock))
})

It("Should not return results", func() {
Expect(actualResult).To(BeNil())
})
})
})

Describe("RemoveTeamFromOrganization", func() {
BeforeEach(func() {
mockOrganizationIdCall(organizationId).Times(1)
httpCall = mockHttpClient.EXPECT().Delete("/roles/assignments/teams", map[string]string{"organizationId": organizationId, "teamId": teamId}).Times(1)
apiClient.RemoveOrganizationRoleFromTeam(teamId)
})

It("Should send DELETE request with assignment id", func() {})
})

Describe("TeamOrganizationAssignments", func() {

Describe("Successful", func() {
var actualResult []OrganizationRoleTeamAssignment
var err error

BeforeEach(func() {
mockOrganizationIdCall(organizationId).Times(1)
httpCall = mockHttpClient.EXPECT().
Get("/roles/assignments/teams", map[string]string{"organizationId": organizationId}, gomock.Any()).
Do(func(path string, request interface{}, response *[]OrganizationRoleTeamAssignment) {
*response = []OrganizationRoleTeamAssignment{*expectedResponse}
}).Times(1)
actualResult, err = apiClient.OrganizationRoleTeamAssignments()

})

It("Should send GET request with params", func() {})

It("should return the GET result", func() {
Expect(actualResult).To(Equal([]OrganizationRoleTeamAssignment{*expectedResponse}))
})

It("Should not return error", func() {
Expect(err).To(BeNil())
})
})

Describe("Failure", func() {
var actualResult []OrganizationRoleTeamAssignment
var err error

BeforeEach(func() {
mockOrganizationIdCall(organizationId).Times(1)
httpCall = mockHttpClient.EXPECT().
Get("/roles/assignments/teams", map[string]string{"organizationId": organizationId}, gomock.Any()).Return(errorMock).Times(1)
actualResult, err = apiClient.OrganizationRoleTeamAssignments()
})

It("Should fail if API call fails", func() {
Expect(err).To(Equal(errorMock))
})

It("Should not return results", func() {
Expect(actualResult).To(BeNil())
})
})
})
})
1 change: 1 addition & 0 deletions env0/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ func Provider(version string) plugin.ProviderFunc {
"env0_provider": resourceProvider(),
"env0_user_environment_assignment": resourceUserEnvironmentAssignment(),
"env0_team_environment_assignment": resourceTeamEnvironmentAssignment(),
"env0_team_organization_assignment": resourceTeamOrganizationAssignment(),
"env0_approval_policy": resourceApprovalPolicy(),
"env0_approval_policy_assignment": resourceApprovalPolicyAssignment(),
"env0_project_budget": resourceProjectBudget(),
Expand Down
112 changes: 112 additions & 0 deletions env0/resource_team_organization_assignment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package env0

import (
"context"

"github.com/env0/terraform-provider-env0/client"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceTeamOrganizationAssignment() *schema.Resource {
return &schema.Resource{
CreateContext: resourceTeamOrganizationAssignmentCreate,
UpdateContext: resourceTeamOrganizationAssignmentUpdate,
ReadContext: resourceTeamOrganizationAssignmentRead,
DeleteContext: resourceTeamOrganizationAssignmentDelete,

Description: "assigns an organization role to a team",

Schema: map[string]*schema.Schema{
"team_id": {
Type: schema.TypeString,
Description: "id of the team",
Required: true,
ForceNew: true,
},
"role_id": {
Type: schema.TypeString,
Description: "id of the assigned role",
Required: true,
ValidateDiagFunc: ValidateNotEmptyString,
},
},
}
}

func resourceTeamOrganizationAssignmentCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
api_client := meta.(client.ApiClientInterface)

var newAssignment client.AssignOrganizationRoleToTeamPayload
if err := readResourceData(&newAssignment, d); err != nil {
return diag.Errorf("schema resource data deserialization failed: %v", err)
}

assignment, err := api_client.AssignOrganizationRoleToTeam(&newAssignment)
if err != nil {
return diag.Errorf("could not create assignment: %v", err)
}

d.SetId(assignment.Id)

return nil
}

func resourceTeamOrganizationAssignmentRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
api_client := meta.(client.ApiClientInterface)

assignments, err := api_client.OrganizationRoleTeamAssignments()
if err != nil {
return diag.Errorf("could not get assignments: %v", err)
}

id := d.Id()

for _, assignment := range assignments {
if assignment.Id == id {
if err := writeResourceData(&assignment, d); err != nil {
return diag.Errorf("schema resource data serialization failed: %v", err)
}

d.Set("role_id", assignment.Role)

return nil
}
}

tflog.Warn(ctx, "Drift Detected: Terraform will remove id from state", map[string]interface{}{"id": d.Id()})
d.SetId("")
Comment on lines +78 to +79
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry can you explain these lines?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ofcourse (:

They way to handle drifts in terraform provider is by resetting the "id" to an empty string.
Once "id" is removed, it will "recreate it"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a Read command and reaching that code means assignment wasn't found among all organization assignments, so clearing the id will force it to be (re)created?
But why is it Drifted? the assignment isn't found in our DB, not in the tf state, no?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's in the tfstate but not in the backend.
So it will add it back to the backend.

If the user doesn't want that, he needs to remove it from the terraform file/state. (This will avoid drift).


return nil
}

func resourceTeamOrganizationAssignmentUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
api_client := meta.(client.ApiClientInterface)

var payload client.AssignOrganizationRoleToTeamPayload
if err := readResourceData(&payload, d); err != nil {
return diag.Errorf("schema resource data deserialization failed: %v", err)
}

assignment, err := api_client.AssignOrganizationRoleToTeam(&payload)
if err != nil {
return diag.Errorf("could not update assignment: %v", err)
}

d.SetId(assignment.Id)

return nil
}

func resourceTeamOrganizationAssignmentDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
api_client := meta.(client.ApiClientInterface)

teamId := d.Get("team_id").(string)

if err := api_client.RemoveOrganizationRoleFromTeam(teamId); err != nil {
return diag.Errorf("could not delete assignment: %v", err)
}

return nil
}
Loading
Loading