Skip to content

Commit

Permalink
Add new resource opsgenie_team_membership, refs #65
Browse files Browse the repository at this point in the history
Signed-off-by: Arnold Bechtoldt <[email protected]>
  • Loading branch information
arnisoph committed Aug 20, 2020
1 parent 1d10d1b commit dbb5597
Show file tree
Hide file tree
Showing 7 changed files with 424 additions and 1 deletion.
1 change: 1 addition & 0 deletions opsgenie/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func Provider() terraform.ResourceProvider {

"opsgenie_team": resourceOpsGenieTeam(),
"opsgenie_team_routing_rule": resourceOpsGenieTeamRoutingRule(),
"opsgenie_team_membership": resourceOpsGenieTeamMembership(),
"opsgenie_user": resourceOpsGenieUser(),
"opsgenie_user_contact": resourceOpsGenieUserContact(),
"opsgenie_notification_policy": resourceOpsGenieNotificationPolicy(),
Expand Down
159 changes: 159 additions & 0 deletions opsgenie/resource_opsgenie_team_membership.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package opsgenie

import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/opsgenie/opsgenie-go-sdk-v2/team"
"log"
"strings"
)

func resourceOpsGenieTeamMembership() *schema.Resource { //TODO encode the e-mail addrs (e.g. because of +)? https://github.com/opsgenie/opsgenie-go-sdk-v2/issues/62
return &schema.Resource{
Create: resourceOpsGenieTeamMembershipCreate,
Read: handleNonExistentResource(resourceOpsGenieTeamMembershipRead),
//Update: resourceOpsGenieTeamMembershipUpdate, // requires https://github.com/opsgenie/opsgenie-go-sdk-v2/issues/59
Delete: resourceOpsGenieTeamMembershipDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"user_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"role": {
Type: schema.TypeString,
Optional: true,
Default: "user",
ForceNew: true,
},
"team_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}

func resourceOpsGenieTeamMembershipCreate(d *schema.ResourceData, meta interface{}) error {

userID := d.Get("user_id").(string)
role := d.Get("role").(string)
teamID := d.Get("team_id").(string)

log.Printf("[INFO] Adding user %q to team %q", teamID, userID)

client, err := team.NewClient(meta.(*OpsgenieClient).client.Config)
if err != nil {
return err
}

// add member to team
_, err = client.AddMember(context.Background(), &team.AddTeamMemberRequest{
TeamIdentifierType: team.Id,
TeamIdentifierValue: teamID,
User: team.User{
ID: userID,
},
Role: role,
})
if err != nil {
return err
}

d.SetId(buildTwoPartID(teamID, userID))

return resourceOpsGenieTeamMembershipRead(d, meta)
}

func resourceOpsGenieTeamMembershipRead(d *schema.ResourceData, meta interface{}) error {

teamID, userID, err := parseTwoPartID(d.Id(), "teamID", "userID")
if err != nil {
return err
}

getRequest := &team.GetTeamRequest{
IdentifierType: team.Id,
IdentifierValue: teamID,
}

log.Printf("[INFO] Retrieving membership of user %q in team %q", userID, teamID)

client, err := team.NewClient(meta.(*OpsgenieClient).client.Config)
if err != nil {
return err
}

getResponse, err := client.Get(context.Background(), getRequest)
if err != nil {
return err
}

role, err := getUserRole(userID, teamID, getResponse.Members)
if err != nil {
return err
}

d.Set("user_id", userID)
d.Set("role", role)
d.Set("team_id", teamID)

return nil
}

func resourceOpsGenieTeamMembershipDelete(d *schema.ResourceData, meta interface{}) error {
userID := d.Get("user_id").(string)
teamID := d.Get("team_id").(string)

log.Printf("[INFO] Deleting membership of user %q in team %q", userID, teamID)

client, err := team.NewClient(meta.(*OpsgenieClient).client.Config)
if err != nil {
return err
}

_, err = client.RemoveMember(context.Background(), &team.RemoveTeamMemberRequest{
TeamIdentifierType: team.Id,
TeamIdentifierValue: teamID,
MemberIdentifierType: team.Id,
MemberIdentifierValue: userID,
})
if err != nil {
return err
}

return nil
}

func getUserRole(userID string, teamID string, input []team.Member) (string, error) {
role := ""

for _, inputMember := range input {
if inputMember.User.ID == userID {
role = inputMember.Role
return role, nil
}
}

return "", fmt.Errorf("did not found user %q in team %q (%#v)", userID, teamID, input)
}

// format the strings into an id `a:b`
func buildTwoPartID(a, b string) string {
return fmt.Sprintf("%s:%s", a, b)
}

// return the pieces of id `left:right` as left, right
func parseTwoPartID(id, left, right string) (string, string, error) {
parts := strings.SplitN(id, ":", 2)
if len(parts) != 2 {
return "", "", fmt.Errorf("unexpected ID format %q, expected %s:%s", id, left, right)
}

return parts[0], parts[1], nil
}
194 changes: 194 additions & 0 deletions opsgenie/resource_opsgenie_team_membershop_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package opsgenie

import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/terraform"
"github.com/opsgenie/opsgenie-go-sdk-v2/team"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
)

func TestAccOpsGenieTeamMembership_basic(t *testing.T) {
rString := acctest.RandString(6)

resource.Test(t, resource.TestCase{
Providers: testAccProviders,

Steps: []resource.TestStep{
{
Config: testAccOpsGenieTeamMembership_basic(rString),
Destroy: false,
Check: resource.ComposeTestCheckFunc(
testCheckOpsGenieTeamExists("opsgenie_team.monkeys"),
testCheckOpsGenieUserExists("opsgenie_user.kong"),
testCheckOpsGenieTeamMembershipExists("opsgenie_team_membership.chaos_kong", "opsgenie_team.monkeys", "opsgenie_user.kong"),
),
},
{
Config: testAccOpsGenieTeamMembership_basicUpdated(rString),
Check: resource.ComposeTestCheckFunc(
testCheckOpsGenieTeamExists("opsgenie_team.monkeys"),
testCheckOpsGenieUserExists("opsgenie_user.kong"),
testCheckOpsGenieTeamMembershipExists("opsgenie_team_membership.chaos_kong", "opsgenie_team.monkeys", "opsgenie_user.kong"),
),
},
{
Config: testAccOpsGenieTeamMembership_basicWithoutMembership(rString),
Check: resource.ComposeTestCheckFunc(
testCheckOpsGenieTeamExists("opsgenie_team.monkeys"),
testCheckOpsGenieUserExists("opsgenie_user.kong"),
testCheckOpsGenieTeamMembershipRemoved("opsgenie_team_membership.chaos_kong", "opsgenie_team.monkeys", "opsgenie_user.kong"),
),
},
},
})
}

func testCheckOpsGenieTeamMembershipExists(membershipResource string, teamResource string, userResource string) resource.TestCheckFunc {
return func(s *terraform.State) error {

rsMembership, ok := s.RootModule().Resources[membershipResource]
if !ok {
return fmt.Errorf("not found: %s", membershipResource)
}

rsTeam, ok := s.RootModule().Resources[teamResource]
if !ok {
return fmt.Errorf("not found: %s", teamResource)
}
teamName := rsTeam.Primary.Attributes["name"]

rsUser, ok := s.RootModule().Resources[userResource]
if !ok {
return fmt.Errorf("not found: %s", userResource)
}
userName := rsUser.Primary.Attributes["username"]

client, err := team.NewClient(testAccProvider.Meta().(*OpsgenieClient).client.Config)
if err != nil {
return err
}
req := team.GetTeamRequest{
IdentifierType: team.Name,
IdentifierValue: teamName,
}
getResponse, err := client.Get(context.Background(), &req)
if err != nil {
return fmt.Errorf("failed to detect team membership for user %q in team %q: %s", userName, teamName, err)
}

// compare what we've actually done
if len(getResponse.Members) != 1 {
return fmt.Errorf("there's no team membership at all. something went wrong :(")
}

if getResponse.Members[0].User.Username != userName {
return fmt.Errorf("expected userName in team membership (%q) doesn't match actual username (%q)", userName, getResponse.Members[0].User.Username)
}

if getResponse.Members[0].User.ID != rsUser.Primary.ID {
return fmt.Errorf("expected user ID in team membership (%q) doesn't match actual username (%q)", rsUser.Primary.ID, getResponse.Members[0].User.ID)
}

if getResponse.Members[0].Role != rsMembership.Primary.Attributes["role"] {
return fmt.Errorf("expected user role in team membership (%q) doesn't match actual user role (%q)", rsMembership.Primary.Attributes["role"], getResponse.Members[0].Role)
}

return nil
}
}

func testCheckOpsGenieTeamMembershipRemoved(membershipResource string, teamResource string, userResource string) resource.TestCheckFunc {
return func(s *terraform.State) error {

_, ok := s.RootModule().Resources[membershipResource]
if ok {
return fmt.Errorf("resource %s still in state. this is bad", membershipResource)
}

rsTeam, ok := s.RootModule().Resources[teamResource]
if !ok {
return fmt.Errorf("not found: %s", teamResource)
}
teamName := rsTeam.Primary.Attributes["name"]

client, err := team.NewClient(testAccProvider.Meta().(*OpsgenieClient).client.Config)
if err != nil {
return err
}
req := team.GetTeamRequest{
IdentifierType: team.Name,
IdentifierValue: teamName,
}
getResponse, err := client.Get(context.Background(), &req)
if err != nil {
return fmt.Errorf("failed to verify team memberships of team %q: %s", teamName, err)
}

// compare what we've actually done
if len(getResponse.Members) != 0 {
return fmt.Errorf("there is still an unexpected number of team membership(s) (%#v). something went wrong :(", getResponse.Members)
}

return nil
}
}

func testAccOpsGenieTeamMembership_basic(rString string) string {
return fmt.Sprintf(`
resource "opsgenie_team" "monkeys" {
name = "monkeys-%s"
description = "They exist."
ignore_members = true
}
resource "opsgenie_user" "kong" {
username = "kong-%[email protected]"
full_name = "Chaos Kong"
role = "User"
}
resource "opsgenie_team_membership" "chaos_kong" {
username = opsgenie_user.kong.username
role = "user"
team = opsgenie_team.monkeys.name
}
`, rString, rString)
}

func testAccOpsGenieTeamMembership_basicUpdated(rString string) string {
return fmt.Sprintf(`
resource "opsgenie_team" "monkeys" {
name = "monkeys-%s"
description = "They exist."
ignore_members = true
}
resource "opsgenie_user" "kong" {
username = "kong-%[email protected]"
full_name = "Chaos Kong"
role = "User"
}
resource "opsgenie_team_membership" "chaos_kong" {
username = opsgenie_user.kong.username
role = "admin"
team = opsgenie_team.monkeys.name
}
`, rString, rString)
}

func testAccOpsGenieTeamMembership_basicWithoutMembership(rString string) string {
return fmt.Sprintf(`
resource "opsgenie_team" "monkeys" {
name = "monkeys-%s"
description = "They exist."
ignore_members = true
depends_on = [opsgenie_user.kong] # Just a hack for the test to destroy resources in the right order
}
resource "opsgenie_user" "kong" {
username = "kong-%[email protected]"
full_name = "Chaos Kong"
role = "User"
}
`, rString, rString)
}
2 changes: 2 additions & 0 deletions opsgenie/resource_opsgenie_user.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ func resourceOpsGenieUser() *schema.Resource {
}

func resourceOpsGenieUserCreate(d *schema.ResourceData, meta interface{}) error {
//TODO OGS-1629: Atlassian systems reset full_name after a certain time after creating a new OpsGenie user.
// This may lead to unexpected behaviour, e.g. when running a subsequent "tf apply" or executing our acceptance tests

client, err := user.NewClient(meta.(*OpsgenieClient).client.Config)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion opsgenie/resource_opsgenie_user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ func testCheckOpsGenieUserExists(name string) resource.TestCheckFunc {
if err != nil {
return fmt.Errorf("Bad: User %q (username: %q) does not exist", id, username)
} else {
log.Printf("User found :%s ", result.Username)
log.Printf("User found: %s", result.Username)
}

return nil
Expand Down
Loading

0 comments on commit dbb5597

Please sign in to comment.