Skip to content

Commit

Permalink
Merge pull request #741 from imjaroiswebdev/TFPROVDEV-47-serv-dep-dri…
Browse files Browse the repository at this point in the history
…ft-detect-on-removal

[TFPROVDEV-47] Address service dependency drift for external removals
  • Loading branch information
imjaroiswebdev authored Aug 30, 2023
2 parents 67a3609 + c91fefe commit 804b00c
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 25 deletions.
57 changes: 32 additions & 25 deletions pagerduty/resource_pagerduty_service_dependency.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package pagerduty

import (
"context"
"fmt"
"log"
"net/http"
"strings"
"sync"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/heimweh/go-pagerduty/pagerduty"
Expand All @@ -19,11 +21,11 @@ var dependencyAssociationMutex sync.Mutex

func resourcePagerDutyServiceDependency() *schema.Resource {
return &schema.Resource{
Create: resourcePagerDutyServiceDependencyAssociate,
Read: resourcePagerDutyServiceDependencyRead,
Delete: resourcePagerDutyServiceDependencyDisassociate,
CreateContext: resourcePagerDutyServiceDependencyAssociate,
ReadContext: resourcePagerDutyServiceDependencyRead,
DeleteContext: resourcePagerDutyServiceDependencyDisassociate,
Importer: &schema.ResourceImporter{
State: resourcePagerDutyServiceDependencyImport,
StateContext: resourcePagerDutyServiceDependencyImport,
},
Schema: map[string]*schema.Schema{
"dependency": {
Expand Down Expand Up @@ -127,15 +129,15 @@ func expandService(v interface{}) *pagerduty.ServiceObj {

return so
}
func resourcePagerDutyServiceDependencyAssociate(d *schema.ResourceData, meta interface{}) error {
func resourcePagerDutyServiceDependencyAssociate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, err := meta.(*Config).Client()
if err != nil {
return err
return diag.FromErr(err)
}

serviceDependency, err := buildServiceDependencyStruct(d)
if err != nil {
return err
return diag.FromErr(err)
}
var r []*pagerduty.ServiceDependency
r = append(r, serviceDependency)
Expand All @@ -146,7 +148,7 @@ func resourcePagerDutyServiceDependencyAssociate(d *schema.ResourceData, meta in
log.Printf("[INFO] Associating PagerDuty dependency %s", serviceDependency.ID)

var dependencies *pagerduty.ListServiceDependencies
retryErr := resource.Retry(5*time.Minute, func() *resource.RetryError {
retryErr := resource.RetryContext(ctx, 5*time.Minute, func() *resource.RetryError {
// Lock the mutex to ensure only one API call to
// `service_dependencies/associate` is done at a time
dependencyAssociationMutex.Lock()
Expand All @@ -170,28 +172,28 @@ func resourcePagerDutyServiceDependencyAssociate(d *schema.ResourceData, meta in
})
if retryErr != nil {
time.Sleep(2 * time.Second)
return retryErr
return diag.FromErr(retryErr)
}
return nil
}

func resourcePagerDutyServiceDependencyDisassociate(d *schema.ResourceData, meta interface{}) error {
func resourcePagerDutyServiceDependencyDisassociate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
client, err := meta.(*Config).Client()
if err != nil {
return err
return diag.FromErr(err)
}

dependency, err := buildServiceDependencyStruct(d)
if err != nil {
return err
return diag.FromErr(err)
}

log.Printf("[INFO] Disassociating PagerDuty dependency %s", dependency.DependentService.ID)

var foundDep *pagerduty.ServiceDependency

// listServiceRelationships by calling get dependencies using the serviceDependency.DependentService.ID
retryErr := resource.Retry(5*time.Minute, func() *resource.RetryError {
retryErr := resource.RetryContext(ctx, 5*time.Minute, func() *resource.RetryError {
if dependencies, _, err := client.ServiceDependencies.GetServiceDependenciesForType(dependency.DependentService.ID, dependency.DependentService.Type); err != nil {
if isErrCode(err, http.StatusBadRequest) {
return resource.NonRetryableError(err)
Expand All @@ -214,7 +216,7 @@ func resourcePagerDutyServiceDependencyDisassociate(d *schema.ResourceData, meta
})
if retryErr != nil {
time.Sleep(5 * time.Second)
return retryErr
return diag.FromErr(retryErr)
}
// If the dependency is not found, then chances are it had been deleted
// outside Terraform or be part of a stale state. So it's needed to be cleared
Expand All @@ -236,7 +238,7 @@ func resourcePagerDutyServiceDependencyDisassociate(d *schema.ResourceData, meta
input := pagerduty.ListServiceDependencies{
Relationships: r,
}
retryErr = resource.Retry(5*time.Minute, func() *resource.RetryError {
retryErr = resource.RetryContext(ctx, 5*time.Minute, func() *resource.RetryError {
if _, _, err = client.ServiceDependencies.DisassociateServiceDependencies(&input); err != nil {
if isErrCode(err, http.StatusBadRequest) {
return resource.NonRetryableError(err)
Expand All @@ -252,21 +254,21 @@ func resourcePagerDutyServiceDependencyDisassociate(d *schema.ResourceData, meta
})
if retryErr != nil {
time.Sleep(5 * time.Second)
return retryErr
return diag.FromErr(retryErr)
}

return nil
}

func resourcePagerDutyServiceDependencyRead(d *schema.ResourceData, meta interface{}) error {
func resourcePagerDutyServiceDependencyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
serviceDependency, err := buildServiceDependencyStruct(d)
if err != nil {
return err
return diag.FromErr(err)
}
log.Printf("[INFO] Reading PagerDuty dependency %s", serviceDependency.ID)

if err = findDependencySetState(d.Id(), serviceDependency.DependentService.ID, serviceDependency.DependentService.Type, d, meta); err != nil {
return err
if err = findDependencySetState(ctx, d.Id(), serviceDependency.DependentService.ID, serviceDependency.DependentService.Type, d, meta); err != nil {
return diag.FromErr(err)
}

return nil
Expand Down Expand Up @@ -307,15 +309,15 @@ func convertType(s string) string {
return s
}

func findDependencySetState(depID, serviceID, serviceType string, d *schema.ResourceData, meta interface{}) error {
func findDependencySetState(ctx context.Context, depID, serviceID, serviceType string, d *schema.ResourceData, meta interface{}) error {
client, err := meta.(*Config).Client()
if err != nil {
return err
}

// Pausing to let the PD API sync.
time.Sleep(1 * time.Second)
retryErr := resource.Retry(5*time.Minute, func() *resource.RetryError {
retryErr := resource.RetryContext(ctx, 5*time.Minute, func() *resource.RetryError {
if dependencies, _, err := client.ServiceDependencies.GetServiceDependenciesForType(serviceID, serviceType); err != nil {
if isErrCode(err, http.StatusBadRequest) {
return resource.NonRetryableError(err)
Expand All @@ -327,15 +329,20 @@ func findDependencySetState(depID, serviceID, serviceType string, d *schema.Reso

return resource.RetryableError(err)
} else if dependencies != nil {
depFound := false
for _, rel := range dependencies.Relationships {
if rel.ID == depID {
d.SetId(rel.ID)
if err := d.Set("dependency", flattenRelationship(rel)); err != nil {
return resource.NonRetryableError(err)
}
d.SetId(rel.ID)
depFound = true
break
}
}
if !depFound {
d.SetId("")
}
}
return nil
})
Expand All @@ -347,15 +354,15 @@ func findDependencySetState(depID, serviceID, serviceType string, d *schema.Reso
return nil
}

func resourcePagerDutyServiceDependencyImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
func resourcePagerDutyServiceDependencyImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
ids := strings.Split(d.Id(), ".")

if len(ids) != 3 {
return []*schema.ResourceData{}, fmt.Errorf("Error importing pagerduty_service_dependency. Expecting an importation ID formed as '<supporting_service_id>.<supporting_service_type>.<service_dependency_id>'")
}
sid, st, id := ids[0], ids[1], ids[2]

if err := findDependencySetState(id, sid, st, d, meta); err != nil {
if err := findDependencySetState(ctx, id, sid, st, d, meta); err != nil {
return []*schema.ResourceData{}, err
}

Expand Down
70 changes: 70 additions & 0 deletions pagerduty/resource_pagerduty_service_dependency_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ func TestAccPagerDutyBusinessServiceDependency_Basic(t *testing.T) {
"pagerduty_service_dependency.foo", "dependency.0.dependent_service.#", "1"),
),
},
// Validating that externally removed business service dependencies are
// detected and planned for re-creation
{
Config: testAccCheckPagerDutyBusinessServiceDependencyConfig(service, businessService, username, email, escalationPolicy),
Check: resource.ComposeTestCheckFunc(
testAccExternallyDestroyServiceDependency("pagerduty_service_dependency.foo", "pagerduty_business_service.foo", "pagerduty_service.foo"),
),
ExpectNonEmptyPlan: true,
},
},
})
}
Expand Down Expand Up @@ -265,6 +274,58 @@ resource "pagerduty_service_dependency" "foo" {
}
`, businessService, username, email, escalationPolicy, service)
}
func testAccExternallyDestroyServiceDependency(resName, depName, suppName string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[resName]
if !ok {
return fmt.Errorf("Not found: %s", resName)
}
if rs.Primary.ID == "" {
return fmt.Errorf("No Service Dependency ID is set for %q", resName)
}

dep, ok := s.RootModule().Resources[depName]
if !ok {
return fmt.Errorf("Not found: %s", depName)
}
if dep.Primary.ID == "" {
return fmt.Errorf("No Dependent Business Service ID is set for %q", depName)
}
depServiceType := dep.Primary.Attributes["type"]

supp, ok := s.RootModule().Resources[suppName]
if !ok {
return fmt.Errorf("Not found: %s", suppName)
}
if supp.Primary.ID == "" {
return fmt.Errorf("No Supporting Service ID is set for %q", suppName)
}
suppServiceType := supp.Primary.Attributes["type"]

client, _ := testAccProvider.Meta().(*Config).Client()
var r []*pagerduty.ServiceDependency
r = append(r, &pagerduty.ServiceDependency{
ID: rs.Primary.ID,
DependentService: &pagerduty.ServiceObj{
ID: dep.Primary.ID,
Type: depServiceType,
},
SupportingService: &pagerduty.ServiceObj{
ID: supp.Primary.ID,
Type: suppServiceType,
},
})
input := pagerduty.ListServiceDependencies{
Relationships: r,
}
_, _, err := client.ServiceDependencies.DisassociateServiceDependencies(&input)
if err != nil {
return err
}

return nil
}
}

// Testing Technical Service Dependencies
func TestAccPagerDutyTechnicalServiceDependency_Basic(t *testing.T) {
Expand All @@ -291,6 +352,15 @@ func TestAccPagerDutyTechnicalServiceDependency_Basic(t *testing.T) {
"pagerduty_service_dependency.bar", "dependency.0.dependent_service.#", "1"),
),
},
// Validating that externally removed technical service dependencies are
// detected and planned for re-creation
{
Config: testAccCheckPagerDutyTechnicalServiceDependencyConfig(dependentService, supportingService, username, email, escalationPolicy),
Check: resource.ComposeTestCheckFunc(
testAccExternallyDestroyServiceDependency("pagerduty_service_dependency.bar", "pagerduty_service.dependBar", "pagerduty_service.supportBar"),
),
ExpectNonEmptyPlan: true,
},
},
})
}
Expand Down

0 comments on commit 804b00c

Please sign in to comment.