From 530410e47c8ee350c0497e26a4d73e4f1207ed9e Mon Sep 17 00:00:00 2001 From: Shotaro Kohama Date: Tue, 8 Feb 2022 22:46:13 -0800 Subject: [PATCH 1/6] feat: update the version of heimweh/go-pagerduty --- go.mod | 2 +- go.sum | 2 ++ .../go-pagerduty/pagerduty/schedule.go | 3 +- .../heimweh/go-pagerduty/pagerduty/user.go | 31 ++++++++++++++++++- .../pagerduty/webhook_subscription.go | 12 +++++-- vendor/modules.txt | 2 +- 6 files changed, 45 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 77b3d5d19..a6c3985cf 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.16 require ( cloud.google.com/go v0.71.0 // indirect github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.1 - github.com/heimweh/go-pagerduty v0.0.0-20211210233744-b65de43109c1 + github.com/heimweh/go-pagerduty v0.0.0-20220208023456-83fe435832fb golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd // indirect google.golang.org/api v0.35.0 // indirect google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb // indirect diff --git a/go.sum b/go.sum index f8b73d3b8..ba73b62d9 100644 --- a/go.sum +++ b/go.sum @@ -282,6 +282,8 @@ github.com/heimweh/go-pagerduty v0.0.0-20211119212911-31ef1eea0d0f h1:/sgh3L7adJ github.com/heimweh/go-pagerduty v0.0.0-20211119212911-31ef1eea0d0f/go.mod h1:JtJGtgN0y9KOCaqFMZFaBCWskpO/KK3Ro9TwjP9ss6w= github.com/heimweh/go-pagerduty v0.0.0-20211210233744-b65de43109c1 h1:49zl3n/g+ff/7/CpxiuqnenyxId5iAjbhgoE9iDHULc= github.com/heimweh/go-pagerduty v0.0.0-20211210233744-b65de43109c1/go.mod h1:JtJGtgN0y9KOCaqFMZFaBCWskpO/KK3Ro9TwjP9ss6w= +github.com/heimweh/go-pagerduty v0.0.0-20220208023456-83fe435832fb h1:p3faOVCU8L4wab9mpxN9Cm/VNVkPD8GMEfD0sPHw9nY= +github.com/heimweh/go-pagerduty v0.0.0-20220208023456-83fe435832fb/go.mod h1:JtJGtgN0y9KOCaqFMZFaBCWskpO/KK3Ro9TwjP9ss6w= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= diff --git a/vendor/github.com/heimweh/go-pagerduty/pagerduty/schedule.go b/vendor/github.com/heimweh/go-pagerduty/pagerduty/schedule.go index f526aaf30..bcf5f3e17 100644 --- a/vendor/github.com/heimweh/go-pagerduty/pagerduty/schedule.go +++ b/vendor/github.com/heimweh/go-pagerduty/pagerduty/schedule.go @@ -58,7 +58,8 @@ type ScheduleLayerEntry struct { // ScheduleLayer represents a schedule layer in a schedule type ScheduleLayer struct { - End string `json:"end,omitempty"` + // End should be nullable because if it's null, it means the layer does not end. + End *string `json:"end"` ID string `json:"id,omitempty"` Name string `json:"name,omitempty"` RenderedCoveragePercentage float64 `json:"rendered_coverage_percentage,omitempty"` diff --git a/vendor/github.com/heimweh/go-pagerduty/pagerduty/user.go b/vendor/github.com/heimweh/go-pagerduty/pagerduty/user.go index a50971588..b6314510a 100644 --- a/vendor/github.com/heimweh/go-pagerduty/pagerduty/user.go +++ b/vendor/github.com/heimweh/go-pagerduty/pagerduty/user.go @@ -3,6 +3,7 @@ package pagerduty import ( "fmt" "log" + "strings" ) // UserService handles the communication with user @@ -289,13 +290,31 @@ func (s *UserService) ListContactMethods(userID string) (*ListContactMethodsResp } // CreateContactMethod creates a new contact method for a user. +// If the same contact method already exists, it will fetch the existing one, return a 200 instead of fail. This feature is useful in terraform +// provider, as when the desired user contact method already exists, terraform will be able to sync it to the state automatically. Otherwise, +// we need to manually fix the conflicts. func (s *UserService) CreateContactMethod(userID string, contactMethod *ContactMethod) (*ContactMethod, *Response, error) { u := fmt.Sprintf("/users/%s/contact_methods", userID) v := new(ContactMethodPayload) resp, err := s.client.newRequestDo("POST", u, nil, &ContactMethodPayload{ContactMethod: contactMethod}, &v) if err != nil { - return nil, nil, err + if e, ok := err.(*Error); ok && strings.Compare(fmt.Sprintf("%v", e.Errors), "[User Contact method must be unique]") == 0 { + resp, _, lErr := s.ListContactMethods(userID) + if lErr != nil { + return nil, nil, fmt.Errorf("user contact method is not unique and failed to fetch existing ones: %w", lErr) + } + + for _, contact := range resp.ContactMethods { + if isSameContactMethod(contact, contactMethod) { + return s.GetContactMethod(userID, contact.ID) + } + } + + return nil, nil, fmt.Errorf("user contact method address is used with different attributes (possibly label)") + } else { + return nil, nil, err + } } if err = cachePutContactMethod(v.ContactMethod); err != nil { @@ -307,6 +326,16 @@ func (s *UserService) CreateContactMethod(userID string, contactMethod *ContactM return v.ContactMethod, resp, nil } +// isSameContactMethod checks if an existing contact method should be taken as the same as a new one users want to create. +// note new contact method misses some fields like Self, HTMLURL. +func isSameContactMethod(existingContact, newContact *ContactMethod) bool { + return existingContact.Type == newContact.Type && + existingContact.Address == newContact.Address && + existingContact.Label == newContact.Label && + existingContact.CountryCode == newContact.CountryCode && + existingContact.Summary == newContact.Summary +} + // GetContactMethod retrieves a contact method for a user. func (s *UserService) GetContactMethod(userID string, contactMethodID string) (*ContactMethod, *Response, error) { u := fmt.Sprintf("/users/%s/contact_methods/%s", userID, contactMethodID) diff --git a/vendor/github.com/heimweh/go-pagerduty/pagerduty/webhook_subscription.go b/vendor/github.com/heimweh/go-pagerduty/pagerduty/webhook_subscription.go index 2f17c17af..8390be7db 100644 --- a/vendor/github.com/heimweh/go-pagerduty/pagerduty/webhook_subscription.go +++ b/vendor/github.com/heimweh/go-pagerduty/pagerduty/webhook_subscription.go @@ -18,9 +18,15 @@ type WebhookSubscription struct { // DeliveryMethod represents a webhook delivery method type DeliveryMethod struct { - TemporarilyDisabled bool `json:"temporarily_disabled,omitempty"` - Type string `json:"type,omitempty"` - URL string `json:"url,omitempty"` + TemporarilyDisabled bool `json:"temporarily_disabled,omitempty"` + Type string `json:"type,omitempty"` + URL string `json:"url,omitempty"` + CustomHeaders []*CustomHeaders `json:"custom_headers,omitempty"` +} + +type CustomHeaders struct { + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` } // Filter represents a webhook subscription filter diff --git a/vendor/modules.txt b/vendor/modules.txt index e28f13ce8..00587e7be 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -124,7 +124,7 @@ github.com/hashicorp/terraform-registry-address github.com/hashicorp/terraform-svchost # github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d github.com/hashicorp/yamux -# github.com/heimweh/go-pagerduty v0.0.0-20211210233744-b65de43109c1 +# github.com/heimweh/go-pagerduty v0.0.0-20220208023456-83fe435832fb ## explicit github.com/heimweh/go-pagerduty/pagerduty # github.com/klauspost/compress v1.11.2 From ddc48ddf422eae3ddf913cb644585a2d4b8cb890 Mon Sep 17 00:00:00 2001 From: Shotaro Kohama Date: Wed, 9 Feb 2022 09:21:47 -0800 Subject: [PATCH 2/6] fix: let a client send `"end":null` when layer.*.end is removed Fix https://github.com/PagerDuty/terraform-provider-pagerduty/issues/451 --- pagerduty/resource_pagerduty_schedule.go | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/pagerduty/resource_pagerduty_schedule.go b/pagerduty/resource_pagerduty_schedule.go index e754f1578..13434e5fb 100644 --- a/pagerduty/resource_pagerduty_schedule.go +++ b/pagerduty/resource_pagerduty_schedule.go @@ -295,7 +295,8 @@ func resourcePagerDutyScheduleUpdate(d *schema.ResourceData, meta interface{}) e if err != nil { return err } - o.End = end.String() + endStr := end.String() + o.End = &endStr schedule.ScheduleLayers = append(schedule.ScheduleLayers, o) } } @@ -360,11 +361,20 @@ func expandScheduleLayers(v interface{}) ([]*pagerduty.ScheduleLayer, error) { return nil, err } + // If End is null, it means the layer does not end. The type of layer.*.end is schema.TypeString. + // A client should send a payload including `"end": null` to unset the end of layer. + // The condition below uses "" because the zero value of schema.TypeString is an empty string. + var end *string + if rsl["end"].(string) != "" { + e := rsl["end"].(string) + end = &e + } + scheduleLayer := &pagerduty.ScheduleLayer{ ID: rsl["id"].(string), Name: rsl["name"].(string), Start: rsl["start"].(string), - End: rsl["end"].(string), + End: end, RotationVirtualStart: rvs.String(), RotationTurnLengthSeconds: rsl["rotation_turn_length_seconds"].(int), } @@ -405,8 +415,8 @@ func flattenScheduleLayers(v []*pagerduty.ScheduleLayer) ([]map[string]interface // A schedule layer can never be removed but it can be ended. // Here we check each layer and if it has been ended we don't read it back // because it's not relevant anymore. - if sl.End != "" { - end, err := timeToUTC(sl.End) + if *sl.End != "" { + end, err := timeToUTC(*sl.End) if err != nil { return nil, err } @@ -418,7 +428,7 @@ func flattenScheduleLayers(v []*pagerduty.ScheduleLayer) ([]map[string]interface scheduleLayer := map[string]interface{}{ "id": sl.ID, "name": sl.Name, - "end": sl.End, + "end": *sl.End, "start": sl.Start, "rotation_virtual_start": sl.RotationVirtualStart, "rotation_turn_length_seconds": sl.RotationTurnLengthSeconds, From 13b485d40c900a6f06a8cd1c095fe455c1ad9b28 Mon Sep 17 00:00:00 2001 From: Shotaro Kohama Date: Wed, 9 Feb 2022 09:52:30 -0800 Subject: [PATCH 3/6] fix: fix the nil pointer dereference error --- pagerduty/resource_pagerduty_schedule.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pagerduty/resource_pagerduty_schedule.go b/pagerduty/resource_pagerduty_schedule.go index 13434e5fb..8a4765e17 100644 --- a/pagerduty/resource_pagerduty_schedule.go +++ b/pagerduty/resource_pagerduty_schedule.go @@ -415,8 +415,10 @@ func flattenScheduleLayers(v []*pagerduty.ScheduleLayer) ([]map[string]interface // A schedule layer can never be removed but it can be ended. // Here we check each layer and if it has been ended we don't read it back // because it's not relevant anymore. - if *sl.End != "" { - end, err := timeToUTC(*sl.End) + slEnd := schema.TypeString.Zero().(string) + if sl.End != nil { + slEnd = *sl.End + end, err := timeToUTC(slEnd) if err != nil { return nil, err } @@ -428,7 +430,7 @@ func flattenScheduleLayers(v []*pagerduty.ScheduleLayer) ([]map[string]interface scheduleLayer := map[string]interface{}{ "id": sl.ID, "name": sl.Name, - "end": *sl.End, + "end": slEnd, "start": sl.Start, "rotation_virtual_start": sl.RotationVirtualStart, "rotation_turn_length_seconds": sl.RotationTurnLengthSeconds, From e1c0f5d41e4937bfcea7a86fe6c8560dd279d823 Mon Sep 17 00:00:00 2001 From: Shotaro Kohama Date: Wed, 9 Feb 2022 09:53:39 -0800 Subject: [PATCH 4/6] refactor: use the zero value of TypeString explicitly --- pagerduty/resource_pagerduty_schedule.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pagerduty/resource_pagerduty_schedule.go b/pagerduty/resource_pagerduty_schedule.go index 8a4765e17..33f20b53f 100644 --- a/pagerduty/resource_pagerduty_schedule.go +++ b/pagerduty/resource_pagerduty_schedule.go @@ -363,9 +363,8 @@ func expandScheduleLayers(v interface{}) ([]*pagerduty.ScheduleLayer, error) { // If End is null, it means the layer does not end. The type of layer.*.end is schema.TypeString. // A client should send a payload including `"end": null` to unset the end of layer. - // The condition below uses "" because the zero value of schema.TypeString is an empty string. var end *string - if rsl["end"].(string) != "" { + if rsl["end"].(string) != schema.TypeString.Zero().(string) { e := rsl["end"].(string) end = &e } From c25782596d4a749c32f09e7fc675fc51f93257ec Mon Sep 17 00:00:00 2001 From: Shotaro Kohama Date: Sun, 13 Feb 2022 11:45:35 -0800 Subject: [PATCH 5/6] refactor: make an util function for a type conversion --- pagerduty/resource_pagerduty_schedule.go | 10 ++-------- pagerduty/util.go | 9 +++++++++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/pagerduty/resource_pagerduty_schedule.go b/pagerduty/resource_pagerduty_schedule.go index 33f20b53f..629048be3 100644 --- a/pagerduty/resource_pagerduty_schedule.go +++ b/pagerduty/resource_pagerduty_schedule.go @@ -361,19 +361,13 @@ func expandScheduleLayers(v interface{}) ([]*pagerduty.ScheduleLayer, error) { return nil, err } - // If End is null, it means the layer does not end. The type of layer.*.end is schema.TypeString. + // The type of layer.*.end is schema.TypeString. If the end is an empty string, it means the layer does not end. // A client should send a payload including `"end": null` to unset the end of layer. - var end *string - if rsl["end"].(string) != schema.TypeString.Zero().(string) { - e := rsl["end"].(string) - end = &e - } - scheduleLayer := &pagerduty.ScheduleLayer{ ID: rsl["id"].(string), Name: rsl["name"].(string), Start: rsl["start"].(string), - End: end, + End: stringTypeToStringPtr(rsl["end"].(string)), RotationVirtualStart: rvs.String(), RotationTurnLengthSeconds: rsl["rotation_turn_length_seconds"].(int), } diff --git a/pagerduty/util.go b/pagerduty/util.go index 878eed813..3b57eeece 100644 --- a/pagerduty/util.go +++ b/pagerduty/util.go @@ -123,3 +123,12 @@ func flattenSlice(v []interface{}) interface{} { } return string(b) } + +// stringTypeToStringPtr is a helper that returns a pointer to +// the string value passed in or nil if the string is empty. +func stringTypeToStringPtr(v string) *string { + if v == "" { + return nil + } + return &v +} From d4d7af38eaa1b95d14c1e5939cf2bf840a4348d5 Mon Sep 17 00:00:00 2001 From: Shotaro Kohama Date: Mon, 14 Feb 2022 07:48:41 -0800 Subject: [PATCH 6/6] refactor: make another util function for a type conversion --- pagerduty/resource_pagerduty_schedule.go | 9 ++++----- pagerduty/util.go | 9 +++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pagerduty/resource_pagerduty_schedule.go b/pagerduty/resource_pagerduty_schedule.go index 41a3fea45..b3df4f741 100644 --- a/pagerduty/resource_pagerduty_schedule.go +++ b/pagerduty/resource_pagerduty_schedule.go @@ -420,10 +420,9 @@ func flattenScheduleLayers(v []*pagerduty.ScheduleLayer) ([]map[string]interface // A schedule layer can never be removed but it can be ended. // Here we check each layer and if it has been ended we don't read it back // because it's not relevant anymore. - slEnd := schema.TypeString.Zero().(string) - if sl.End != nil { - slEnd = *sl.End - end, err := timeToUTC(slEnd) + endStr := stringPtrToStringType(sl.End) + if endStr != "" { + end, err := timeToUTC(endStr) if err != nil { return nil, err } @@ -435,7 +434,7 @@ func flattenScheduleLayers(v []*pagerduty.ScheduleLayer) ([]map[string]interface scheduleLayer := map[string]interface{}{ "id": sl.ID, "name": sl.Name, - "end": slEnd, + "end": endStr, "start": sl.Start, "rotation_virtual_start": sl.RotationVirtualStart, "rotation_turn_length_seconds": sl.RotationTurnLengthSeconds, diff --git a/pagerduty/util.go b/pagerduty/util.go index 3b57eeece..e8cb33ea9 100644 --- a/pagerduty/util.go +++ b/pagerduty/util.go @@ -132,3 +132,12 @@ func stringTypeToStringPtr(v string) *string { } return &v } + +// stringPtrToStringType is a helper that returns the string value passed in +// or an empty string if the given pointer is nil. +func stringPtrToStringType(v *string) string { + if v == nil { + return "" + } + return *v +}