diff --git a/go.mod b/go.mod index 11be425bc..e37dd44b3 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/hashicorp/terraform-exec v0.16.0 github.com/hashicorp/terraform-json v0.13.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.11.0 - github.com/heimweh/go-pagerduty v0.0.0-20231207205722-b4c4cc9f249e + github.com/heimweh/go-pagerduty v0.0.0-20231212192829-0de11cddf326 ) require ( diff --git a/go.sum b/go.sum index ac258abcd..ab6558b0b 100644 --- a/go.sum +++ b/go.sum @@ -250,8 +250,8 @@ github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/heimweh/go-pagerduty v0.0.0-20231207205722-b4c4cc9f249e h1:u8v+2ZZOb9QFGiE676aDj08N9kFTNHcMtY6+9g57+ko= -github.com/heimweh/go-pagerduty v0.0.0-20231207205722-b4c4cc9f249e/go.mod h1:r59w5iyN01Qvi734yA5hZldbSeJJmsJzee/1kQ/MK7s= +github.com/heimweh/go-pagerduty v0.0.0-20231212192829-0de11cddf326 h1:ZyL8A1yPg0C9rUKc/QCUNM848NJ9DhTE/cRZH54y/5s= +github.com/heimweh/go-pagerduty v0.0.0-20231212192829-0de11cddf326/go.mod h1:r59w5iyN01Qvi734yA5hZldbSeJJmsJzee/1kQ/MK7s= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 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= diff --git a/vendor/github.com/heimweh/go-pagerduty/pagerduty/pagerduty.go b/vendor/github.com/heimweh/go-pagerduty/pagerduty/pagerduty.go index 0c3aef5dd..9e74693ca 100644 --- a/vendor/github.com/heimweh/go-pagerduty/pagerduty/pagerduty.go +++ b/vendor/github.com/heimweh/go-pagerduty/pagerduty/pagerduty.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "log" + "math/rand" "net/http" "net/url" "strconv" @@ -23,6 +24,7 @@ const ( defaultAppOauthTokenGenerationURL = "https://identity.pagerduty.com/oauth/token" defaultUserAgent = "heimweh/go-pagerduty(terraform)" defaultRegion = "us" + jitterPercent = 0.3 ) // AuthTokenType is an enum of available tokens types @@ -336,7 +338,7 @@ func (c *Client) newRequestDoContext(ctx context.Context, method, url string, qr resp, err := c.do(req, v) if err != nil { if respErr, ok := err.(*Error); ok && respErr.needToRetry { - return c.newRequestDoContext(ctx, method, url, qryOptions, body, v) + return c.newRequestDoContext(ctx, method, url, nil, body, v) } return nil, err @@ -368,7 +370,7 @@ func (c *Client) newRequestDoOptionsContext(ctx context.Context, method, url str resp, err := c.do(req, v) if err != nil { if respErr, ok := err.(*Error); ok && respErr.needToRetry { - return c.newRequestDoOptionsContext(ctx, method, url, qryOptions, body, v) + return c.newRequestDoOptionsContext(ctx, method, url, nil, body, v) } return nil, err @@ -570,21 +572,23 @@ func (c *Client) decodeErrorResponse(res *Response) error { v := &errorResponse{Error: &Error{ErrorResponse: res}} err := c.DecodeJSON(res, v) - // Delaying retry based on ratelimit-reset recommended by PagerDuty - // https://developer.pagerduty.com/docs/72d3b724589e3-rest-api-rate-limits#reaching-the-limit - ratelimitReset := res.Response.Header.Get("ratelimit-reset") - if res.Response.StatusCode == http.StatusTooManyRequests && ratelimitReset != "" { - waitFor, err := strconv.ParseInt(ratelimitReset, 10, 0) - if err == nil { - reqMethod := res.Response.Request.Method - reqEndpoint := res.Response.Request.URL - log.Printf("[INFO] Rate limit hit, throttling by %d seconds until next retry to %s: %s", waitFor, strings.ToUpper(reqMethod), reqEndpoint) - time.Sleep(time.Duration(waitFor) * time.Second) - v.Error.needToRetry = true - return v.Error - } + if handledError := handleRatelimitError(res, v); handledError != nil { + return handledError + } + + if handledError := c.handleScopedOAuthError(res, v); handledError != nil { + return handledError + } + + if err != nil { + return fmt.Errorf("%s API call to %s failed: %v", res.Response.Request.Method, res.Response.Request.URL.String(), res.Response.Status) } + log.Printf("[INFO] v.Error %+v", v.Error) + + return v.Error +} +func (c *Client) handleScopedOAuthError(res *Response, v *errorResponse) error { isUsingScopedAPITokenFromCredentials := *c.Config.APIAuthTokenType == AuthTokenTypeUseAppCredentials isOauthScopeMissing := isUsingScopedAPITokenFromCredentials && res.Response.StatusCode == http.StatusForbidden needNewOauthScopedAccessToken := isUsingScopedAPITokenFromCredentials && res.Response.StatusCode == http.StatusUnauthorized @@ -600,12 +604,51 @@ func (c *Client) decodeErrorResponse(res *Response) error { return v.Error } - if err != nil { - return fmt.Errorf("%s API call to %s failed: %v", res.Response.Request.Method, res.Response.Request.URL.String(), res.Response.Status) + return nil +} + +// handleRatelimitError will handle rate limit errors from responses with http +// code 429. Delaying retry based on ratelimit-reset recommended by PagerDuty +// https://developer.pagerduty.com/docs/72d3b724589e3-rest-api-rate-limits#reaching-the-limit +func handleRatelimitError(res *Response, v *errorResponse) error { + var markErrorAsRetryable = func(waitFor time.Duration) error { + reqMethod := res.Response.Request.Method + reqEndpoint := res.Response.Request.URL + log.Printf( + "[INFO] Rate limit hit, throttling by %v seconds until next retry to %s: %s", + strconv.FormatFloat(waitFor.Seconds(), 'f', 1, 64), + strings.ToUpper(reqMethod), + reqEndpoint) + time.Sleep(waitFor) + v.Error.needToRetry = true + return v.Error } - log.Printf("[INFO] v.Error %+v", v.Error) - return v.Error + ratelimitReset := res.Response.Header.Get("ratelimit-reset") + + if res.Response.StatusCode == http.StatusTooManyRequests && ratelimitReset == "" { + baseDelay := 5 * time.Second + jitter := 1 + (jitterPercent * rand.Float64()) + waitFor := time.Duration(float64(baseDelay) * jitter) + + return markErrorAsRetryable(waitFor) + } + + if res.Response.StatusCode == http.StatusTooManyRequests && ratelimitReset != "" { + headerWaitSeconds, err := strconv.ParseInt(ratelimitReset, 10, 0) + if err == nil { + baseDelay := 500 * time.Millisecond + headerWait := time.Duration(headerWaitSeconds) * time.Second + jitter := 1 + (jitterPercent * rand.Float64()) + extraWait := time.Duration(float64(baseDelay) * jitter) + + waitFor := headerWait + extraWait + + return markErrorAsRetryable(waitFor) + } + } + + return nil } func availableOauthScopes() []string { diff --git a/vendor/modules.txt b/vendor/modules.txt index c90d1773a..f587c7ad5 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -182,7 +182,7 @@ github.com/hashicorp/terraform-svchost # github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d ## explicit github.com/hashicorp/yamux -# github.com/heimweh/go-pagerduty v0.0.0-20231207205722-b4c4cc9f249e +# github.com/heimweh/go-pagerduty v0.0.0-20231212192829-0de11cddf326 ## explicit; go 1.17 github.com/heimweh/go-pagerduty/pagerduty github.com/heimweh/go-pagerduty/persistentconfig