From cf36419c55b2810be5b316dd368adb5c9d9d794f Mon Sep 17 00:00:00 2001 From: Sergiu Ghitea <28300158+sergiught@users.noreply.github.com> Date: Fri, 25 Aug 2023 15:14:45 +0200 Subject: [PATCH] DXCDT-520: Use a custom client with retries for auth0 api client config (#788) --- go.mod | 2 +- go.sum | 21 +++ .../auth0/resourceserver/data_source_test.go | 5 +- internal/config/client_retry_test.go | 175 ++++++++++++++++++ internal/config/config.go | 99 +++++++++- 5 files changed, 299 insertions(+), 3 deletions(-) create mode 100644 internal/config/client_retry_test.go diff --git a/go.mod b/go.mod index 889929fc1..a4ac2297c 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/auth0/terraform-provider-auth0 go 1.21 require ( + github.com/PuerkitoBio/rehttp v1.2.0 github.com/auth0/go-auth0 v1.0.0 github.com/google/go-cmp v0.5.9 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 @@ -19,7 +20,6 @@ require ( github.com/Masterminds/semver/v3 v3.1.1 // indirect github.com/Masterminds/sprig/v3 v3.2.2 // indirect github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect - github.com/PuerkitoBio/rehttp v1.2.0 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/armon/go-radix v1.0.0 // indirect diff --git a/go.sum b/go.sum index 05d05ac45..f022e6603 100644 --- a/go.sum +++ b/go.sum @@ -6,11 +6,13 @@ github.com/Masterminds/sprig/v3 v3.2.1/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFP github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= github.com/PuerkitoBio/rehttp v1.2.0 h1:V8MGVcDwR+u/xwLlMrw5YZONDm3JISEKqwJTiIuJA+s= github.com/PuerkitoBio/rehttp v1.2.0/go.mod h1:LUwKPoDbDIA2RL5wYZCNsQ90cx4OJ4AWBmq6KzWZL1s= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= +github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= @@ -36,14 +38,20 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= +github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= github.com/go-git/go-git/v5 v5.6.1 h1:q4ZRqQl4pR/ZJHc1L5CFjGA1a10u76aV1iC+nh+bHsk= +github.com/go-git/go-git/v5 v5.6.1/go.mod h1:mvyoL6Unz0PiTQrGQfSfiLFhBH1c1e84ylC2MDs4ee8= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -112,15 +120,22 @@ github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= +github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= @@ -148,19 +163,23 @@ github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0= +github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= @@ -181,6 +200,7 @@ github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/zclconf/go-cty v1.13.2 h1:4GvrUxe/QUDYuJKAav4EYqdM47/kZa672LwmXFmEKT0= github.com/zclconf/go-cty v1.13.2/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -242,6 +262,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/dnaeon/go-vcr.v3 v3.1.2 h1:F1smfXBqQqwpVifDfUBQG6zzaGjzT+EnVZakrOdr5wA= gopkg.in/dnaeon/go-vcr.v3 v3.1.2/go.mod h1:2IMOnnlx9I6u9x+YBsM3tAMx6AlOxnJ0pWxQAzZ79Ag= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/auth0/resourceserver/data_source_test.go b/internal/auth0/resourceserver/data_source_test.go index 50b2eb2ae..4c7719526 100644 --- a/internal/auth0/resourceserver/data_source_test.go +++ b/internal/auth0/resourceserver/data_source_test.go @@ -162,7 +162,10 @@ func TestAccDataSourceResourceServer(t *testing.T) { resource.TestCheckResourceAttr("data.auth0_resource_server.auth0", "allow_offline_access", "false"), resource.TestCheckResourceAttr("data.auth0_resource_server.auth0", "signing_alg", "RS256"), resource.TestCheckResourceAttr("data.auth0_resource_server.auth0", "token_lifetime_for_web", "7200"), - resource.TestCheckResourceAttr("data.auth0_resource_server.auth0", "scopes.#", "140"), + resource.TestCheckTypeSetElemNestedAttrs("data.auth0_resource_server.auth0", "scopes.*", map[string]string{ + "name": "read:users", + "description": "Read Users", + }), // Checking just one to ensure that scopes are not empty, as they get expanded periodically. resource.TestCheckResourceAttr("data.auth0_resource_server.auth0", "verification_location", ""), resource.TestCheckResourceAttr("data.auth0_resource_server.auth0", "enforce_policies", "false"), resource.TestCheckResourceAttr("data.auth0_resource_server.auth0", "token_dialect", ""), diff --git a/internal/config/client_retry_test.go b/internal/config/client_retry_test.go new file mode 100644 index 000000000..ad16161fc --- /dev/null +++ b/internal/config/client_retry_test.go @@ -0,0 +1,175 @@ +package config + +import ( + "crypto/tls" + "crypto/x509" + "errors" + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCustomClientWithRetries(t *testing.T) { + t.Run("it retries on rate limit error", func(t *testing.T) { + apiCalls := 0 + fail := true + testServer := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + apiCalls++ + + if fail { + fail = false + writer.WriteHeader(429) + resetAt := time.Now().Add(time.Second).Unix() + writer.Header().Set("X-RateLimit-Reset", strconv.Itoa(int(resetAt))) + return + } + + writer.WriteHeader(200) + })) + + client := customClientWithRetries() + + request, err := http.NewRequest(http.MethodGet, testServer.URL, nil) + require.NoError(t, err) + + response, err := client.Do(request) + require.NoError(t, err) + + assert.Equal(t, 200, response.StatusCode) + assert.False(t, fail) + assert.Equal(t, 2, apiCalls) + + t.Cleanup(func() { + testServer.Close() + err := response.Body.Close() + require.NoError(t, err) + }) + }) + + t.Run("it retries on server error", func(t *testing.T) { + apiCalls := 0 + fail := true + testServer := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + apiCalls++ + + if fail { + fail = false + writer.WriteHeader(500) + return + } + + writer.WriteHeader(200) + })) + + client := customClientWithRetries() + + request, err := http.NewRequest(http.MethodGet, testServer.URL, nil) + require.NoError(t, err) + + response, err := client.Do(request) + require.NoError(t, err) + + assert.Equal(t, 200, response.StatusCode) + assert.False(t, fail) + assert.Equal(t, 2, apiCalls) + + t.Cleanup(func() { + testServer.Close() + err := response.Body.Close() + require.NoError(t, err) + }) + }) + + t.Run("it does not retry more than 3 times on server error", func(t *testing.T) { + apiCalls := 0 + testServer := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + apiCalls++ + writer.WriteHeader(500) + })) + + client := customClientWithRetries() + + request, err := http.NewRequest(http.MethodGet, testServer.URL, nil) + require.NoError(t, err) + + response, err := client.Do(request) + require.NoError(t, err) + + assert.Equal(t, 500, response.StatusCode) + assert.Equal(t, 3+1, apiCalls) // 3 retries + 1 first call. + + t.Cleanup(func() { + testServer.Close() + err := response.Body.Close() + require.NoError(t, err) + }) + }) +} + +func TestRetryableErrorRetryFunc(t *testing.T) { + testCases := []struct { + name string + err error + expected bool + }{ + { + name: "NilError", + err: nil, + expected: false, + }, + { + name: "TooManyRedirectsError", + err: &url.Error{ + Op: "Get", + URL: "http://example.com", + Err: errors.New("stopped after 5 redirects"), + }, + expected: false, + }, + { + name: "UnsupportedProtocolSchemeError", + err: &url.Error{ + Op: "Get", + URL: "ftp://example.com", + Err: errors.New("unsupported protocol scheme"), + }, + expected: false, + }, + { + name: "CertificateVerificationError", + err: &url.Error{ + Op: "Get", + URL: "https://example.com", + Err: &tls.CertificateVerificationError{}, + }, + expected: false, + }, + { + name: "UnknownAuthorityError", + err: &url.Error{ + Op: "Get", + URL: "https://example.com", + Err: x509.UnknownAuthorityError{}, + }, + expected: false, + }, + { + name: "OtherError", + err: errors.New("some other error"), + expected: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + actual := retryableErrorRetryFunc(testCase.err) + assert.Equal(t, testCase.expected, actual) + }) + } +} diff --git a/internal/config/config.go b/internal/config/config.go index 7e323760c..2749f1a82 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -2,9 +2,16 @@ package config import ( "context" + "crypto/tls" + "crypto/x509" "fmt" "net/http" + "net/url" + "regexp" + "strconv" + "time" + "github.com/PuerkitoBio/rehttp" "github.com/auth0/go-auth0" "github.com/auth0/go-auth0/management" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -60,7 +67,8 @@ func ConfigureProvider(terraformVersion *string) schema.ConfigureContextFunc { management.WithDebug(debug), management.WithUserAgent(userAgent(terraformVersion)), management.WithAuth0ClientEnvEntry(providerName, version), - management.WithRetries(3, []int{http.StatusTooManyRequests, http.StatusInternalServerError}), + management.WithNoRetries(), + management.WithClient(customClientWithRetries()), ) if err != nil { return nil, diag.FromErr(err) @@ -106,3 +114,92 @@ func authenticationOption(clientID, clientSecret, apiToken, audience string) man return management.WithClientCredentials(ctx, clientID, clientSecret) } + +func customClientWithRetries() *http.Client { + client := &http.Client{ + Transport: rateLimitTransport( + retryableErrorTransport( + http.DefaultTransport, + ), + ), + } + + return client +} + +func rateLimitTransport(tripper http.RoundTripper) http.RoundTripper { + return rehttp.NewTransport(tripper, rateLimitRetry, rateLimitDelay) +} + +func rateLimitRetry(attempt rehttp.Attempt) bool { + if attempt.Response == nil { + return false + } + + return attempt.Response.StatusCode == http.StatusTooManyRequests +} + +func rateLimitDelay(attempt rehttp.Attempt) time.Duration { + resetAt := attempt.Response.Header.Get("X-RateLimit-Reset") + + resetAtUnix, err := strconv.ParseInt(resetAt, 10, 64) + if err != nil { + resetAtUnix = time.Now().Add(5 * time.Second).Unix() + } + + return time.Duration(resetAtUnix-time.Now().Unix()) * time.Second +} + +func retryableErrorTransport(tripper http.RoundTripper) http.RoundTripper { + return rehttp.NewTransport( + tripper, + rehttp.RetryAll( + rehttp.RetryMaxRetries(3), + rehttp.RetryAny( + rehttp.RetryStatuses( + http.StatusServiceUnavailable, + http.StatusInternalServerError, + http.StatusBadGateway, + http.StatusGatewayTimeout, + // Cloudflare-specific server error that is generated + // because Cloudflare did not receive an HTTP response + // from the origin server after an HTTP Connection was made. + 524, + ), + rehttp.RetryIsErr(retryableErrorRetryFunc), + ), + ), + rehttp.ExpJitterDelay(500*time.Millisecond, 10*time.Second), + ) +} + +func retryableErrorRetryFunc(err error) bool { + if err == nil { + return false + } + + if v, ok := err.(*url.Error); ok { + // Don't retry if the error was due to too many redirects. + if regexp.MustCompile(`stopped after \d+ redirects\z`).MatchString(v.Error()) { + return false + } + + // Don't retry if the error was due to an invalid protocol scheme. + if regexp.MustCompile(`unsupported protocol scheme`).MatchString(v.Error()) { + return false + } + + // Don't retry if the certificate issuer is unknown. + if _, ok := v.Err.(*tls.CertificateVerificationError); ok { + return false + } + + // Don't retry if the certificate issuer is unknown. + if _, ok := v.Err.(x509.UnknownAuthorityError); ok { + return false + } + } + + // The error is likely recoverable so retry. + return true +}