From 7728036aafd34d828e170ff6e47d962884f22ed8 Mon Sep 17 00:00:00 2001 From: DJ Date: Thu, 3 Jun 2021 07:30:41 +0900 Subject: [PATCH] New resource: `rollbar_team_user_association` (#63) * Implement new resource: rollbar_team_user_association * don't set resource ID with the status --- .github/workflows/tests.yml | 4 +- TESTING.md | 6 +- docs/resources/pagerduty_integration.md | 1 - docs/resources/project_access_token.md | 7 - docs/resources/team.md | 6 +- docs/resources/team_project_association.md | 1 - docs/resources/team_user_association.md | 81 +++++ go.mod | 2 +- go.sum | 4 +- helper/test/config.go | 14 + rollbar/errors.go | 6 + rollbar/helpers.go | 12 + ...port_rollbar_team_user_association_test.go | 43 +++ rollbar/provider.go | 1 + ...source_rollbar_team_project_association.go | 2 +- .../resource_rollbar_team_user_association.go | 283 ++++++++++++++++++ ...urce_rollbar_team_user_association_test.go | 78 +++++ 17 files changed, 533 insertions(+), 18 deletions(-) create mode 100644 docs/resources/team_user_association.md create mode 100644 rollbar/errors.go create mode 100644 rollbar/import_rollbar_team_user_association_test.go create mode 100644 rollbar/resource_rollbar_team_user_association.go create mode 100644 rollbar/resource_rollbar_team_user_association_test.go diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 89148a6..922ad21 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -40,4 +40,6 @@ jobs: ROLLBAR_ACCOUNT_ACCESS_TOKEN: ${{ secrets.ROLLBAR_ACCOUNT_ACCESS_TOKEN }} ROLLBAR_PD_API_KEY: ${{ secrets.ROLLBAR_PD_API_KEY }} ROLLBAR_PROJECT_ACCESS_TOKEN: ${{ secrets.ROLLBAR_PROJECT_ACCESS_TOKEN }} - ROLLBAR_USER_EMAIL: ${{ secrets.ROLLBAR_USER_EMAIL }} \ No newline at end of file + ROLLBAR_USER_EMAIL: ${{ secrets.ROLLBAR_USER_EMAIL }} + ROLLBAR_TEAM_ID: ${{ secrets.ROLLBAR_TEAM_ID }} + ROLLBAR_EMAIL_ADDRESS: ${{ secrets.ROLLBAR_EMAIL_ADDRESS }} \ No newline at end of file diff --git a/TESTING.md b/TESTING.md index 5592d54..c82da70 100644 --- a/TESTING.md +++ b/TESTING.md @@ -33,7 +33,11 @@ The following parameters are available for running the test. The absence of some * **TF_ACC** (`integer`) **Required** - must be set to `1`. * **ROLLBAR_ACCOUNT_ACCESS_TOKEN** (`string`) - The account access token of the user running the test. -* **ROLLBAR_PROJECT_ACCESS_TOKEN** (`string`) - The account access token of the user running the test. +* **ROLLBAR_PROJECT_ACCESS_TOKEN** (`string`) - The project access token of the user running the test. +* **ROLLBAR_PD_API_KEY** (`string`) - A PagerDuty API key. +* **ROLLBAR_USER_EMAIL** (`string`) - A Rollbar user email address. +* **ROLLBAR_TEAM_ID** (`string`) - The ID of a rollbar team. +* **ROLLBAR_EMAIL_ADDRESS** (`string`) - An email address. Please note: if you run the entire acceptance suite, you will need to set BOTH `ROLLBAR_ACCOUNT_ACCESS_TOKEN` & `ROLLBAR_PROJECT_ACCESS_TOKEN`. Otherwise, certain tests require either token. diff --git a/docs/resources/pagerduty_integration.md b/docs/resources/pagerduty_integration.md index 857bfd1..118e11a 100644 --- a/docs/resources/pagerduty_integration.md +++ b/docs/resources/pagerduty_integration.md @@ -34,7 +34,6 @@ resource "rollbar_pagerduty_integration" "pd" { The following arguments are supported: * `service_key` - (Required) `` Valid PagerDuty Service API Key. Must 32 characters long. - * `enabled` - (Required) `` Enable the PagerDuty notifications globally ## Attributes Reference diff --git a/docs/resources/project_access_token.md b/docs/resources/project_access_token.md index 054ed56..953fb0c 100644 --- a/docs/resources/project_access_token.md +++ b/docs/resources/project_access_token.md @@ -51,18 +51,13 @@ resource "rollbar_project_access_token" "foobar" { The following arguments are supported: * `project_id` - (Required) `` The ID of the project - * `name` - (Required) `` Name of the project access token. Max length 32 characters. - * `scopes` - (Required) `` Scopes to assign to the create access token. Valid options: `read`, `write`, `post_server_item`, `post_client_item`. - * `status` - (Required) `` Enable or disable the access token. Valid options: `enabled`, `disabled`. - * `rate_limit_window_size` `` - Period of time (in seconds) for the rate limit. On **resource creation only**, the valid options are the following: `0, 60, 300, 1800, 3600, 86400, 604800, 2592000`. Otherwise, any value greater than `0`. If this argument is not set, the default is 60 seconds (1 minute). - * `rate_limit_window_count` `` - Number of requests for the defined rate limiting period. Otherwise, any value greater than `0`. If this argument is not set, the default is 5000 calls. @@ -71,9 +66,7 @@ Otherwise, any value greater than `0`. If this argument is not set, the default The following attributes are exported: * `cur_rate_limit_window_count` - How many remaining API calls are left for the access token. - * `date_created` - The timestamp in epoch of when the token was created. - * `access_token` - The actual access token. This value is set to `Sensitive` and will not be shown in any non-debug `terraform` outputs. diff --git a/docs/resources/team.md b/docs/resources/team.md index 8bccfaf..19331fd 100644 --- a/docs/resources/team.md +++ b/docs/resources/team.md @@ -10,7 +10,8 @@ description: |- This resource is used to create and manage teams on Rollbar. -**NOTE:** The Rollbar API does not support updating existing teams, only through the UI. +-> **IMPORTANT!** +The Rollbar API does not support updating existing teams, only through the UI. Therefore, you must update your configuration file(s) for this resource if you manually updated the team name. Otherwise, your `terraform plan` will detect if a difference between the state file and remote. @@ -18,7 +19,7 @@ the team name. Otherwise, your `terraform plan` will detect if a difference betw ```hcl-terraform # Create a new Rollbar team -resource "rollbar_team" "follbar" { +resource "rollbar_team" "foobar" { name = "my_new_team" access_level = "standard" } @@ -29,7 +30,6 @@ resource "rollbar_team" "follbar" { The following arguments are supported: * `name` - (Required) `` Name of the team. - * `access_level` - (Required) `` Access level of the team. Valid options: `standard`, `light`, `view`. `standard` is the only access level you can choose in the UI. `light` and `view` are API-only team access levels. `light` gives the team read and write access, but not to all settings. `view` gives the team read-only access. diff --git a/docs/resources/team_project_association.md b/docs/resources/team_project_association.md index 33476ba..68e9b60 100644 --- a/docs/resources/team_project_association.md +++ b/docs/resources/team_project_association.md @@ -33,7 +33,6 @@ resource "rollbar_team_project_association" "foobar" { The following arguments are supported: * `team_id` - (Required) `` ID of existing team. - * `project_id` - (Required) `` ID of existing project. ## Attributes Reference diff --git a/docs/resources/team_user_association.md b/docs/resources/team_user_association.md new file mode 100644 index 0000000..4aadeb2 --- /dev/null +++ b/docs/resources/team_user_association.md @@ -0,0 +1,81 @@ +--- +layout: "rollbar" +page_title: "Rollbar: rollbar_team_user_association" +sidebar_current: "docs-rollbar-resource-team-user-association" +description: |- +Provides a resource to create and manage the association between a team and user. +--- + +# rollbar_team_user_association + +This resource is used to create and manage the association between a team and user. + +### Unique resource lifecycle + +If the specified `email` belongs to a new user: + +* For resource creation, an invitation will be sent to the user to create a Rollbar account. + Once the account is created, the user will join the team. +* For resource deletion, if the invitation has been accepted, and the user has joined the team, + the user will be removed from the team. If the invitation has not been accepted, the invitation + will be revoked. + +If the specified `email` belongs to an existing Rollbar user: + +* For resource creation, the user will be immediately added to the team. +* For resource deletion, the user will be removed from the team. + +## Example Usage + +```hcl-terraform +data "rollbar_team" "foobar" { + id = "my_team_id" +} + +data "rollbar_user" "foobar" { + email = "user@email.com" +} + +resource "rollbar_team_user_association" "foobar" { + team_id = data.rollbar_team.foobar.id + email = data.rollbar_user.foobar.email +} +``` + +```hcl-terraform +resource "rollbar_team_user_association" "foobar" { + team_id = 123456 + email = "email_to_invite@company.com" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `team_id` - (Required) `` ID of existing team. +* `email` - (Required) `` Email address of an existing Rollbar user or a new user. + +## Attributes Reference + +* `user_id` - Email address of a user. +* `invited_or_added` - Whether the user was either initially `invited` or `added` + to the Rollbar team. +* `invitation_status` - Status of the invitation. This attribute is set only if the user + had to first be invited to Rollbar in order to join the team. +* `invitation_id` - ID of the invitation. This attribute is set only if the user + had to first be invited to Rollbar in order to join the team. + +## Import + +Existing team user association can be imported using a composite value of the team ID and email address +separated by a colon. + +For example: + +```shell +$ terraform import rollbar_team_user_association.follbar 123:user@company.com +``` + +-> **IMPORTANT!** +You can only import a team user association if the user has already joined the team. \ No newline at end of file diff --git a/go.mod b/go.mod index 68e5793..9a2604d 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/davidji99/terraform-provider-rollbar go 1.16 require ( - github.com/davidji99/rollrest-go v0.1.5 + github.com/davidji99/rollrest-go v0.1.7 github.com/hashicorp/terraform-plugin-sdk/v2 v2.6.1 github.com/stretchr/testify v1.7.0 ) diff --git a/go.sum b/go.sum index 23c6d1e..534e37a 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidji99/go-querystring v1.0.2 h1:zZUgCkhdFEsb0b1tS1zhn7FwjoRyp33J3be1av5sBi0= github.com/davidji99/go-querystring v1.0.2/go.mod h1:67KzURpYgsT0d/eszawoSvtpI+34vczasEK3xgrxrqA= -github.com/davidji99/rollrest-go v0.1.5 h1:a2XfxyUY/LDJVaJOmBqxNgWf89qvCUpBkCZXn9N3GgI= -github.com/davidji99/rollrest-go v0.1.5/go.mod h1:avSXFeDrY9utfUC8HO8ooVFAf0Mugm2N9qhzdkBa01g= +github.com/davidji99/rollrest-go v0.1.7 h1:LdQtpoZJ9jyBm3pf0b0v2oc2mmKsDEcIiYlc0Wz6rfE= +github.com/davidji99/rollrest-go v0.1.7/go.mod h1:avSXFeDrY9utfUC8HO8ooVFAf0Mugm2N9qhzdkBa01g= github.com/davidji99/simpleresty v0.2.3 h1:oUHimRSFUgGgJjd+65q3L9alzPF3vFBiXHKxVVmkvkU= github.com/davidji99/simpleresty v0.2.3/go.mod h1:v1honpRzBWA0G2Ivpz3ozeaB4zRiYxvu8CAAAtAlHqY= github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= diff --git a/helper/test/config.go b/helper/test/config.go index 818f959..27d70cb 100644 --- a/helper/test/config.go +++ b/helper/test/config.go @@ -9,15 +9,21 @@ import ( const ( TestConfigAccountAccessToken TestConfigKey = iota + TestConfigProjectAccessToken TestConfigAcceptanceTestKey TestConfigPagerDutyAPIKey TestConfigUserEmailKey + TestConfigTeamID + TestConfigEmailAddress ) var testConfigKeyToEnvName = map[TestConfigKey]string{ TestConfigAccountAccessToken: "ROLLBAR_ACCOUNT_ACCESS_TOKEN", + TestConfigProjectAccessToken: "ROLLBAR_PROJECT_ACCESS_TOKEN", TestConfigPagerDutyAPIKey: "ROLLBAR_PD_API_KEY", TestConfigUserEmailKey: "ROLLBAR_USER_EMAIL", + TestConfigTeamID: "ROLLBAR_TEAM_ID", + TestConfigEmailAddress: "ROLLBAR_EMAIL_ADDRESS", TestConfigAcceptanceTestKey: resource.TestEnvVar, } @@ -78,3 +84,11 @@ func (t *TestConfig) GetUserEmailOrAbort(testing *testing.T) (val string) { func (t *TestConfig) GetPagerDutyAPIKeyorAbort(testing *testing.T) (val string) { return t.GetOrAbort(testing, TestConfigPagerDutyAPIKey) } + +func (t *TestConfig) GetTeamIDorAbort(testing *testing.T) (val string) { + return t.GetOrAbort(testing, TestConfigTeamID) +} + +func (t *TestConfig) GetTeamEmailAddress(testing *testing.T) (val string) { + return t.GetOrAbort(testing, TestConfigEmailAddress) +} diff --git a/rollbar/errors.go b/rollbar/errors.go new file mode 100644 index 0000000..369753e --- /dev/null +++ b/rollbar/errors.go @@ -0,0 +1,6 @@ +package rollbar + +// UserNotFoundError is returned when searching for a user by a certain criteria. +type UserNotFoundError struct { + error +} diff --git a/rollbar/helpers.go b/rollbar/helpers.go index 320fe24..12abbc5 100644 --- a/rollbar/helpers.go +++ b/rollbar/helpers.go @@ -10,6 +10,18 @@ import ( "time" ) +// getEmail extracts the email attribute generically from a Rollbar resource. +func getEmail(d *schema.ResourceData) string { + var email string + if v, ok := d.GetOk("email"); ok { + vs := v.(string) + log.Printf("[DEBUG] email: %s", vs) + email = vs + } + + return email +} + // getTeamID extracts the team ID attribute generically from a Rollbar resource. func getTeamID(d *schema.ResourceData) int { var teamID int diff --git a/rollbar/import_rollbar_team_user_association_test.go b/rollbar/import_rollbar_team_user_association_test.go new file mode 100644 index 0000000..49671ba --- /dev/null +++ b/rollbar/import_rollbar_team_user_association_test.go @@ -0,0 +1,43 @@ +package rollbar + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "testing" +) + +func TestAccRollbarTeamUserAssociationTest_importBasic(t *testing.T) { + teamID := testAccConfig.GetTeamIDorAbort(t) + email := testAccConfig.GetTeamEmailAddress(t) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckRollbarTeamUserAssociation_basic(teamID, email), + }, + { + ResourceName: "rollbar_team_user_association.foobar", + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: testAccRollbarTeamUserAssociationImportStateIdFunc("rollbar_team_user_association.foobar"), + }, + }, + }) +} + +func testAccRollbarTeamUserAssociationImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("not found: %s", resourceName) + } + + return fmt.Sprintf("%s:%s", rs.Primary.Attributes["team_id"], + rs.Primary.Attributes["email"]), nil + } +} diff --git a/rollbar/provider.go b/rollbar/provider.go index f5d09a8..e059248 100644 --- a/rollbar/provider.go +++ b/rollbar/provider.go @@ -50,6 +50,7 @@ func Provider() *schema.Provider { "rollbar_project_access_token": resourceRollbarProjectAccessToken(), "rollbar_team": resourceRollbarTeam(), "rollbar_team_project_association": resourceRollbarTeamProjectAssociation(), + "rollbar_team_user_association": resourceRollbarTeamUserAssociation(), }, ConfigureContextFunc: providerConfigure, diff --git a/rollbar/resource_rollbar_team_project_association.go b/rollbar/resource_rollbar_team_project_association.go index 74e4ce7..9bd2c4d 100644 --- a/rollbar/resource_rollbar_team_project_association.go +++ b/rollbar/resource_rollbar_team_project_association.go @@ -75,7 +75,7 @@ func resourceRollbarTeamProjectAssociationCreate(ctx context.Context, d *schema. d.SetId(fmt.Sprintf("%d:%d", int(result.GetResult().GetTeamID()), int(result.GetResult().GetProjectID()))) - return diags + return resourceRollbarTeamProjectAssociationRead(ctx, d, meta) } func resourceRollbarTeamProjectAssociationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { diff --git a/rollbar/resource_rollbar_team_user_association.go b/rollbar/resource_rollbar_team_user_association.go new file mode 100644 index 0000000..30fd535 --- /dev/null +++ b/rollbar/resource_rollbar_team_user_association.go @@ -0,0 +1,283 @@ +package rollbar + +import ( + "context" + "fmt" + "github.com/davidji99/rollrest-go/rollrest" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "log" + "regexp" + "strconv" +) + +const ( + TeamUserAddedStatus = "added" + TeamUserInvitedStatus = "invited" +) + +func resourceRollbarTeamUserAssociation() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceRollbarTeamUserAssociationCreate, + ReadContext: resourceRollbarTeamUserAssociationRead, + DeleteContext: resourceRollbarTeamUserAssociationDelete, + + Importer: &schema.ResourceImporter{ + StateContext: resourceRollbarTeamUserAssociationImport, + }, + + Schema: map[string]*schema.Schema{ + "team_id": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + + "email": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "user_id": { + Type: schema.TypeInt, + Computed: true, + }, + + "invited_or_added": { + Type: schema.TypeString, + Computed: true, + }, + + "invitation_status": { + Type: schema.TypeString, + Computed: true, + }, + + "invitation_id": { + Type: schema.TypeInt, + Computed: true, + }, + }, + } +} + +func resourceRollbarTeamUserAssociationImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + client := meta.(*Config).API + + result, parseErr := ParseCompositeID(d.Id(), 2) + if parseErr != nil { + return nil, parseErr + } + + teamID, _ := strconv.Atoi(result[0]) + email := result[1] + + // Retrieve user ID by email + user, _, userFindErr := findUserByEmail(client, email) + if userFindErr != nil { + return nil, fmt.Errorf("did not find an existing Rollbar user with email %s", email) + } + + userID := int(user.GetID()) + + // Check if user has been added to team + isMember, _, err := client.Teams.IsUserMember(teamID, userID) + if err != nil { + return nil, err + } + + if !isMember { + return nil, fmt.Errorf("cannot import - user %d has not been added to team %d", userID, teamID) + } + + d.SetId(constructTeamUserResourceID(teamID, email)) + d.Set("email", email) + d.Set("user_id", int(user.GetID())) + d.Set("invited_or_added", TeamUserAddedStatus) + d.Set("invitation_status", "") + d.Set("invitation_id", 0) + + return []*schema.ResourceData{d}, nil +} + +func findUserByEmail(client *rollrest.Client, email string) (*rollrest.User, bool, error) { + users, _, userInfoErr := client.Users.List() + if userInfoErr != nil { + return nil, false, userInfoErr + } + + for _, u := range users.GetResult().Users { + if u.GetEmail() == email { + return u, true, nil + } + } + + return nil, false, nil +} + +func constructTeamUserResourceID(teamID int, email string) string { + return fmt.Sprintf("%d:%s", teamID, email) +} + +func resourceRollbarTeamUserAssociationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + client := meta.(*Config).API + teamID := getTeamID(d) + email := getEmail(d) + + log.Printf("[DEBUG] Inviting or adding %s to team %d", email, teamID) + + inviteResponse, _, inviteErr := client.Teams.InviteUser(teamID, &rollrest.TeamInviteRequest{Email: email}) + if inviteErr != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: fmt.Sprintf("unable to invite/add %s to team %d", email, teamID), + Detail: inviteErr.Error(), + }) + return diags + } + + log.Printf("[DEBUG] Invited or added %s to team %d", email, teamID) + + var resourceID string + var invitedOrAdded string + + // If the invite response returns the following message string, the email address belongs to an existing Rollbar user, + // and that user will be immediately added to the team. + if regexp.MustCompile(`given email address has been added`).MatchString(inviteResponse.GetMessage()) { + resourceID = constructTeamUserResourceID(teamID, email) + invitedOrAdded = TeamUserAddedStatus + } + + // If the invite response returns an invitation, the email address has been sent an invitation and the user needs + // to accept it before being added to the team. + if inviteResponse.GetResult() != nil { + resourceID = constructTeamUserResourceID(teamID, email) + invitedOrAdded = TeamUserInvitedStatus + } + + if resourceID == "" { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: fmt.Sprintf("Invite/added %s to team %d but the API response is not expected. Likely a provider issue.", + email, teamID), + Detail: fmt.Sprintf("response error count: %d | message: %s | result: %v", + inviteResponse.GetErrorCount(), inviteResponse.GetMessage(), inviteResponse.GetResult()), + }) + return diags + } + + d.SetId(resourceID) + + // This will either be the actual invitation ID or an empty string + d.Set("invitation_id", int(inviteResponse.GetResult().GetID())) + d.Set("invited_or_added", invitedOrAdded) + + return resourceRollbarTeamUserAssociationRead(ctx, d, meta) +} + +func resourceRollbarTeamUserAssociationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + client := meta.(*Config).API + + result, parseErr := ParseCompositeID(d.Id(), 2) + if parseErr != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to parse resource ID during state refresh", + Detail: parseErr.Error(), + }) + return diags + } + + teamID, _ := strconv.Atoi(result[0]) + email := result[1] + invitedOrAdded := d.Get("invited_or_added").(string) + + d.Set("team_id", teamID) + d.Set("email", email) + d.Set("invitation_status", "") + + if invitedOrAdded == TeamUserAddedStatus || d.Get("invitation_status").(string) == "accepted" { + user, _, userFindErr := findUserByEmail(client, email) + if userFindErr != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: fmt.Sprintf("cannot determine if %s exists in Rollbar", email), + Detail: userFindErr.Error(), + }) + return diags + } + d.Set("user_id", int(user.GetID())) + } + + if invitedOrAdded == TeamUserInvitedStatus { + inviteID := d.Get("invitation_id").(int) + inviteStatus, _, statusErr := client.Invitations.Get(inviteID) + if statusErr != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: fmt.Sprintf("unable to retrieve invitation %d during state refresh", inviteID), + Detail: statusErr.Error(), + }) + return diags + } + d.Set("invitation_status", inviteStatus.GetResult().GetStatus()) + } + + return diags +} + +func resourceRollbarTeamUserAssociationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + client := meta.(*Config).API + + result, parseErr := ParseCompositeID(d.Id(), 2) + if parseErr != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "unable to parse resource ID during state refresh", + Detail: parseErr.Error(), + }) + return diags + } + + teamID, _ := strconv.Atoi(result[0]) + email := result[1] + invitedOrAdded := d.Get("invited_or_added").(string) + + if invitedOrAdded == TeamUserInvitedStatus { + inviteID := d.Get("invitation_id").(int) + + log.Printf("[DEBUG] Cancelling invitation %d", inviteID) + + // Cancel the invitation + _, _, cancelErr := client.Invitations.Cancel(d.Get("invitation_id").(int)) + if cancelErr != nil { + log.Printf("[DEBUG] issue cancelling invitation but that's okay as subsequent invitations to the same email address will invalidate any pending ones") + } + + log.Printf("[DEBUG] Cancelled invitation %d", inviteID) + } + + if invitedOrAdded == TeamUserAddedStatus { + log.Printf("[DEBUG] Removing %s from team %d", email, teamID) + + _, _, removeErr := client.Teams.RemoveUser(teamID, d.Get("user_id").(int)) + if removeErr != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: fmt.Sprintf("could not remove %s from team %d", email, teamID), + Detail: removeErr.Error(), + }) + return diags + } + + log.Printf("[DEBUG] Removed %s from team %d", email, teamID) + } + + d.SetId("") + + return diags +} diff --git a/rollbar/resource_rollbar_team_user_association_test.go b/rollbar/resource_rollbar_team_user_association_test.go new file mode 100644 index 0000000..9c5700d --- /dev/null +++ b/rollbar/resource_rollbar_team_user_association_test.go @@ -0,0 +1,78 @@ +package rollbar + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "strings" + "testing" +) + +func TestAccRollbarTeamUserAssociation_BasicInvited(t *testing.T) { + teamID := testAccConfig.GetTeamIDorAbort(t) + + emailSplitted := strings.Split(testAccConfig.GetTeamEmailAddress(t), "@") + email := fmt.Sprintf("%s+%s@%s", emailSplitted[0], acctest.RandString(10), emailSplitted[1]) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckRollbarTeamUserAssociation_basic(teamID, email), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "rollbar_team_user_association.foobar", "team_id", teamID), + resource.TestCheckResourceAttr( + "rollbar_team_user_association.foobar", "email", email), + //resource.TestCheckResourceAttrSet( + // "rollbar_team_user_association.foobar", "user_id"), + resource.TestCheckResourceAttr( + "rollbar_team_user_association.foobar", "invited_or_added", "invited"), + resource.TestCheckResourceAttrSet( + "rollbar_team_user_association.foobar", "invitation_status"), + resource.TestCheckResourceAttrSet( + "rollbar_team_user_association.foobar", "invitation_id"), + ), + }, + }, + }) +} + +func TestAccRollbarTeamUserAssociation_BasicAdded(t *testing.T) { + teamID := testAccConfig.GetTeamIDorAbort(t) + email := testAccConfig.GetTeamEmailAddress(t) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckRollbarTeamUserAssociation_basic(teamID, email), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "rollbar_team_user_association.foobar", "team_id", teamID), + resource.TestCheckResourceAttr( + "rollbar_team_user_association.foobar", "email", email), + resource.TestCheckResourceAttrSet( + "rollbar_team_user_association.foobar", "user_id"), + resource.TestCheckResourceAttr( + "rollbar_team_user_association.foobar", "invited_or_added", "added"), + //resource.TestCheckResourceAttrSet( + // "rollbar_team_user_association.foobar", "invitation_status"), + //resource.TestCheckResourceAttrSet( + // "rollbar_team_user_association.foobar", "invitation_id"), + ), + }, + }, + }) +} + +func testAccCheckRollbarTeamUserAssociation_basic(teamID, email string) string { + return fmt.Sprintf(` +resource "rollbar_team_user_association" "foobar" { + team_id = %s + email = "%s" +} +`, teamID, email) +}