Skip to content

Commit

Permalink
add service dep clean up after external removal
Browse files Browse the repository at this point in the history
Add test cases for validating patched case, additionally update
`pagerduty_service_dependency` implementation to use context aware
methods.
  • Loading branch information
imjaroiswebdev committed Aug 30, 2023
1 parent 67a3609 commit c91fefe
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 c91fefe

Please sign in to comment.