@@ -21,6 +21,7 @@ import (
2121 "errors"
2222 "fmt"
2323 "io"
24+ "math"
2425 "net/http"
2526 "strings"
2627 "time"
@@ -49,6 +50,8 @@ var rateLimitInterval = 30 * time.Second
4950const (
5051 PRODUCER_ID string = "guacsec/guac"
5152 CDCollector string = "clearlydefined"
53+ maxRetries = 10
54+ baseDelay = 1 * time .Second
5255)
5356
5457var ErrComponentTypeMismatch error = errors .New ("rootComponent type is not []*root_package.PackageNode" )
@@ -72,7 +75,7 @@ func NewClearlyDefinedHTTPClient(limiter *rate.Limiter) *http.Client {
7275}
7376
7477// getDefinitions uses the coordinates to query clearly defined for license definition
75- func getDefinitions (_ context.Context , client * http.Client , purls []string , coordinates []string ) (map [string ]* attestation.Definition , error ) {
78+ func getDefinitions (ctx context.Context , client * http.Client , purls []string , coordinates []string ) (map [string ]* attestation.Definition , error ) {
7679
7780 coordinateToPurl := make (map [string ]string )
7881 for i , purl := range purls {
@@ -87,30 +90,17 @@ func getDefinitions(_ context.Context, client *http.Client, purls []string, coor
8790 return nil , fmt .Errorf ("error marshalling coordinates: %w" , err )
8891 }
8992
90- // retries if a 429 is encountered. This could occur even with the rate limiting
91- // as multiple services may be hitting it.
92- var resp * http.Response
93- maxRetries := 5
94- for retries := 0 ; retries < maxRetries ; retries ++ {
95- // Make the POST request
96- resp , err = client .Post ("https://api.clearlydefined.io/definitions" , "application/json" , bytes .NewBuffer (jsonData ))
97- if err != nil {
98- return nil , fmt .Errorf ("error making POST request: %w" , err )
99- }
100- defer resp .Body .Close ()
101-
102- if resp .StatusCode != http .StatusOK {
103- if resp .StatusCode != http .StatusTooManyRequests {
104- // otherwise return an error
105- return nil , fmt .Errorf ("unexpected status code: %d" , resp .StatusCode )
106- }
107- } else {
108- break
109- }
93+ // retries if a 429 is encountered
94+ backoffOperation := func () (* http.Response , error ) {
95+ return client .Post ("https://api.clearlydefined.io/definitions" , "application/json" , bytes .NewBuffer (jsonData ))
96+ }
11097
111- // Retry after a delay if status code is 429
112- time .Sleep (5 * time .Second )
98+ wrappedOperation := retryWithBackoff (ctx , backoffOperation )
99+ resp , err := wrappedOperation ()
100+ if err != nil {
101+ return nil , fmt .Errorf ("clearly defined POST request failed with error: %w" , err )
113102 }
103+ defer resp .Body .Close ()
114104
115105 body , err := io .ReadAll (resp .Body )
116106 if err != nil {
@@ -300,3 +290,35 @@ func createAttestation(purl string, definition *attestation.Definition, currentT
300290
301291 return attestation
302292}
293+
294+ // retryFunc is a function that can be retried
295+ type retryFunc func () (* http.Response , error )
296+
297+ // retryWithBackoff retries the given operation with exponential backoff
298+ func retryWithBackoff (ctx context.Context , operation retryFunc ) retryFunc {
299+ logger := logging .FromContext (ctx )
300+ return func () (* http.Response , error ) {
301+ var collectedResp * http.Response
302+ for i := 0 ; i < maxRetries ; i ++ {
303+ resp , err := operation ()
304+ if err != nil {
305+ return nil , fmt .Errorf ("error making POST request: %w" , err )
306+ }
307+ if resp .StatusCode != http .StatusOK {
308+ if resp .StatusCode != http .StatusTooManyRequests {
309+ // otherwise return an error
310+ return nil , fmt .Errorf ("unexpected status code: %d" , resp .StatusCode )
311+ } else {
312+ secRetry := math .Pow (2 , float64 (i ))
313+ logger .Infof ("Retrying operation in %f seconds\n " , secRetry )
314+ delay := time .Duration (secRetry ) * baseDelay
315+ time .Sleep (delay )
316+ }
317+ } else {
318+ collectedResp = resp
319+ break
320+ }
321+ }
322+ return collectedResp , nil
323+ }
324+ }
0 commit comments