diff --git a/pagerduty/resource_pagerduty_tag_assignment.go b/pagerduty/resource_pagerduty_tag_assignment.go index 3d8f805b6..6a822ed65 100644 --- a/pagerduty/resource_pagerduty_tag_assignment.go +++ b/pagerduty/resource_pagerduty_tag_assignment.go @@ -3,6 +3,7 @@ package pagerduty import ( "fmt" "log" + "net/http" "strings" "time" @@ -106,6 +107,15 @@ func resourcePagerDutyTagAssignmentRead(d *schema.ResourceData, meta interface{} log.Printf("[INFO] Reading PagerDuty tag assignment with tagID %s for %s entity with ID %s", assignment.TagID, assignment.EntityType, assignment.EntityID) + ok, err := isFoundTagAssignmentEntity(d.Get("entity_id").(string), d.Get("entity_type").(string), meta) + if err != nil { + return err + } + if !ok { + d.SetId("") + return nil + } + return resource.Retry(30*time.Second, func() *resource.RetryError { if tagResponse, _, err := client.Tags.ListTagsForEntity(assignment.EntityType, assignment.EntityID); err != nil { time.Sleep(2 * time.Second) @@ -202,6 +212,50 @@ func resourcePagerDutyTagAssignmentImport(d *schema.ResourceData, meta interface return []*schema.ResourceData{d}, err } +func isFoundTagAssignmentEntity(entityID, entityType string, meta interface{}) (bool, error) { + var isFound bool + client, err := meta.(*Config).Client() + if err != nil { + return isFound, err + } + + fetchUser := func(id string) (*pagerduty.User, *pagerduty.Response, error) { + return client.Users.Get(id, &pagerduty.GetUserOptions{}) + } + fetchTeam := func(id string) (*pagerduty.Team, *pagerduty.Response, error) { + return client.Teams.Get(id) + } + fetchEscalationPolicy := func(id string) (*pagerduty.EscalationPolicy, *pagerduty.Response, error) { + return client.EscalationPolicies.Get(id, &pagerduty.GetEscalationPolicyOptions{}) + } + retryErr := resource.Retry(30*time.Second, func() *resource.RetryError { + var err error + if entityType == "users" { + _, _, err = fetchUser(entityID) + } + if entityType == "teams" { + _, _, err = fetchTeam(entityID) + } + if entityType == "escalation_policies" { + _, _, err = fetchEscalationPolicy(entityID) + } + + if err != nil && isErrCode(err, http.StatusNotFound) { + return nil + } + if err != nil { + return resource.RetryableError(err) + } + isFound = true + + return nil + }) + if retryErr != nil { + return isFound, retryErr + } + return isFound, nil +} + func createAssignmentID(entityID, tagID string) string { return fmt.Sprintf("%v.%v", entityID, tagID) } diff --git a/pagerduty/resource_pagerduty_tag_assignment_test.go b/pagerduty/resource_pagerduty_tag_assignment_test.go index e6a7cfc82..c97ff209a 100644 --- a/pagerduty/resource_pagerduty_tag_assignment_test.go +++ b/pagerduty/resource_pagerduty_tag_assignment_test.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/heimweh/go-pagerduty/pagerduty" ) func TestAccPagerDutyTagAssignment_User(t *testing.T) { @@ -32,6 +33,24 @@ func TestAccPagerDutyTagAssignment_User(t *testing.T) { "pagerduty_user.foo", "email", email), ), }, + // Validating that externally removed users with tag assigments are + // detected and tag assignment is planed for re-creation + { + Config: testAccCheckPagerDutyTagAssignmentConfig(tagLabel, username, email), + Check: resource.ComposeTestCheckFunc( + testAccExternallyDestroyTagAssignment("pagerduty_user.foo", "users"), + ), + ExpectNonEmptyPlan: true, + }, + { + Config: testAccCheckPagerDutyTagAssignmentConfig(tagLabel, username, email), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckNoResourceAttr( + "pagerduty_tag_assignment.foo", "id"), + ), + PlanOnly: true, + ExpectNonEmptyPlan: true, + }, }, }) } @@ -54,6 +73,24 @@ func TestAccPagerDutyTagAssignment_Team(t *testing.T) { "pagerduty_team.foo", "name", team), ), }, + // Validating that externally removed teams with tag assigments are + // detected and tag assignment is planed for re-creation + { + Config: testAccCheckPagerDutyTagAssignmentTeamConfig(tagLabel, team), + Check: resource.ComposeTestCheckFunc( + testAccExternallyDestroyTagAssignment("pagerduty_team.foo", "teams"), + ), + ExpectNonEmptyPlan: true, + }, + { + Config: testAccCheckPagerDutyTagAssignmentTeamConfig(tagLabel, team), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckNoResourceAttr( + "pagerduty_tag_assignment.foo", "id"), + ), + PlanOnly: true, + ExpectNonEmptyPlan: true, + }, }, }) } @@ -71,13 +108,31 @@ func TestAccPagerDutyTagAssignment_EP(t *testing.T) { { Config: testAccCheckPagerDutyTagAssignmentEPConfig(tagLabel, username, email, ep), Check: resource.ComposeTestCheckFunc( - testAccCheckPagerDutyTagAssignmentExists("pagerduty_tag_assignment.foo", "teams"), + testAccCheckPagerDutyTagAssignmentExists("pagerduty_tag_assignment.foo", "escalation_policies"), resource.TestCheckResourceAttr( "pagerduty_tag.foo", "label", tagLabel), resource.TestCheckResourceAttr( "pagerduty_escalation_policy.foo", "name", ep), ), }, + // Validating that externally removed escalation policies with tag + // assigments are detected and tag assignment is planed for re-creation + { + Config: testAccCheckPagerDutyTagAssignmentEPConfig(tagLabel, username, email, ep), + Check: resource.ComposeTestCheckFunc( + testAccExternallyDestroyTagAssignment("pagerduty_escalation_policy.foo", "escalation_policies"), + ), + ExpectNonEmptyPlan: true, + }, + { + Config: testAccCheckPagerDutyTagAssignmentEPConfig(tagLabel, username, email, ep), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckNoResourceAttr( + "pagerduty_tag_assignment.foo", "id"), + ), + PlanOnly: true, + ExpectNonEmptyPlan: true, + }, }, }) } @@ -200,9 +255,47 @@ resource "pagerduty_escalation_policy" "foo" { } } resource "pagerduty_tag_assignment" "foo" { - entity_type = "teams" + entity_type = "escalation_policies" entity_id = pagerduty_escalation_policy.foo.id tag_id = pagerduty_tag.foo.id } `, tagLabel, username, email, ep) } + +func testAccExternallyDestroyTagAssignment(n, entityType string) resource.TestCheckFunc { + deleteUser := func(id string, client *pagerduty.Client) (*pagerduty.Response, error) { + return client.Users.Delete(id) + } + deleteTeam := func(id string, client *pagerduty.Client) (*pagerduty.Response, error) { + return client.Teams.Delete(id) + } + deleteEscalationPolicy := func(id string, client *pagerduty.Client) (*pagerduty.Response, error) { + return client.EscalationPolicies.Delete(id) + } + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No Tag Assignment ID is set") + } + + client, _ := testAccProvider.Meta().(*Config).Client() + var err error + if entityType == "users" { + _, err = deleteUser(rs.Primary.ID, client) + } + if entityType == "teams" { + _, err = deleteTeam(rs.Primary.ID, client) + } + if entityType == "escalation_policies" { + _, err = deleteEscalationPolicy(rs.Primary.ID, client) + } + if err != nil { + return err + } + + return nil + } +} diff --git a/pagerduty/resource_pagerduty_team.go b/pagerduty/resource_pagerduty_team.go index 7c5f71d19..28395e5b7 100644 --- a/pagerduty/resource_pagerduty_team.go +++ b/pagerduty/resource_pagerduty_team.go @@ -94,8 +94,11 @@ func resourcePagerDutyTeamRead(d *schema.ResourceData, meta interface{}) error { return resource.Retry(30*time.Second, func() *resource.RetryError { if team, _, err := client.Teams.Get(d.Id()); err != nil { - time.Sleep(2 * time.Second) - return resource.RetryableError(err) + errResp := handleNotFoundError(err, d) + if errResp != nil { + time.Sleep(2 * time.Second) + return resource.RetryableError(errResp) + } } else if team != nil { d.Set("name", team.Name) d.Set("description", team.Description) diff --git a/pagerduty/resource_pagerduty_team_test.go b/pagerduty/resource_pagerduty_team_test.go index 9de51be9b..7e513e91f 100644 --- a/pagerduty/resource_pagerduty_team_test.go +++ b/pagerduty/resource_pagerduty_team_test.go @@ -83,6 +83,24 @@ func TestAccPagerDutyTeam_Basic(t *testing.T) { "pagerduty_team.foo", "description", "bar"), ), }, + // Validating that externally removed teams are detected and planed for + // re-creation + { + Config: testAccCheckPagerDutyTeamConfigUpdated(teamUpdated), + Check: resource.ComposeTestCheckFunc( + testAccExternallyDestroyTeam("pagerduty_team.foo"), + ), + ExpectNonEmptyPlan: true, + }, + { + Config: testAccCheckPagerDutyTeamConfigUpdated(teamUpdated), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckNoResourceAttr( + "pagerduty_team.foo", "id"), + ), + PlanOnly: true, + ExpectNonEmptyPlan: true, + }, }, }) } @@ -173,3 +191,23 @@ resource "pagerduty_team" "foo" { parent = pagerduty_team.parent.id }`, parent, team) } + +func testAccExternallyDestroyTeam(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + if rs.Primary.ID == "" { + return fmt.Errorf("No Team ID is set") + } + + client, _ := testAccProvider.Meta().(*Config).Client() + _, err := client.Teams.Delete(rs.Primary.ID) + if err != nil { + return err + } + + return nil + } +}