@@ -14,6 +14,7 @@ import (
14
14
"net/url"
15
15
"runtime"
16
16
"sort"
17
+ "time"
17
18
18
19
"github.com/elastic/elastic-agent/pkg/testing"
19
20
"github.com/elastic/elastic-agent/pkg/version"
@@ -29,6 +30,9 @@ const (
29
30
30
31
artifactElasticAgentProject = "elastic-agent-package"
31
32
artifactReleaseCDN = "https://artifacts.elastic.co/downloads/beats/elastic-agent"
33
+
34
+ maxAttemptsForArtifactsAPICall = 6
35
+ retryIntervalForArtifactsAPICall = 5 * time .Second
32
36
)
33
37
34
38
var (
@@ -137,14 +141,17 @@ type ArtifactAPIClient struct {
137
141
c httpDoer
138
142
url string
139
143
cdnURL string
144
+
145
+ logger logger
140
146
}
141
147
142
148
// NewArtifactAPIClient creates a new Artifact API client
143
- func NewArtifactAPIClient (opts ... ArtifactAPIClientOpt ) * ArtifactAPIClient {
149
+ func NewArtifactAPIClient (logger logger , opts ... ArtifactAPIClientOpt ) * ArtifactAPIClient {
144
150
c := & ArtifactAPIClient {
145
151
url : defaultArtifactAPIURL ,
146
152
cdnURL : artifactReleaseCDN ,
147
153
c : new (http.Client ),
154
+ logger : logger ,
148
155
}
149
156
150
157
for _ , opt := range opts {
@@ -310,9 +317,37 @@ func (aac ArtifactAPIClient) createAndPerformRequest(ctx context.Context, URL st
310
317
return nil , err
311
318
}
312
319
313
- resp , err := aac .c .Do (req )
320
+ // Make the request with retries as the artifacts API can sometimes be flaky.
321
+ var resp * http.Response
322
+ // TODO (once we're on Go 1.22): replace with for numAttempts := range maxAttemptsForArtifactsAPICall {
323
+ for numAttempts := 0 ; numAttempts < maxAttemptsForArtifactsAPICall ; numAttempts ++ {
324
+ resp , err = aac .c .Do (req )
325
+
326
+ // If there is no error, no need to retry the request.
327
+ if err == nil {
328
+ break
329
+ }
330
+
331
+ // If the context was cancelled or timed out, return early
332
+ if errors .Is (err , context .Canceled ) || errors .Is (err , context .DeadlineExceeded ) {
333
+ return nil , fmt .Errorf (
334
+ "executing http request %s %s (attempt %d of %d) cancelled or timed out: %w" ,
335
+ req .Method , req .URL , numAttempts + 1 , maxAttemptsForArtifactsAPICall , err ,
336
+ )
337
+ }
338
+
339
+ aac .logger .Logf (
340
+ "failed attempt %d of %d executing http request %s %s: %s; retrying after %v..." ,
341
+ numAttempts + 1 , maxAttemptsForArtifactsAPICall , req .Method , req .URL , err .Error (), retryIntervalForArtifactsAPICall ,
342
+ )
343
+ time .Sleep (retryIntervalForArtifactsAPICall )
344
+ }
345
+
314
346
if err != nil {
315
- return nil , fmt .Errorf ("executing http request %v: %w" , req , err )
347
+ return nil , fmt .Errorf (
348
+ "failed executing http request %s %s after %d attempts: %w" ,
349
+ req .Method , req .URL , maxAttemptsForArtifactsAPICall , err ,
350
+ )
316
351
}
317
352
318
353
return resp , nil
@@ -341,7 +376,7 @@ type logger interface {
341
376
Logf (format string , args ... any )
342
377
}
343
378
344
- func (aac ArtifactAPIClient ) GetLatestSnapshotVersion (ctx context.Context , log logger ) (* version.ParsedSemVer , error ) {
379
+ func (aac ArtifactAPIClient ) GetLatestSnapshotVersion (ctx context.Context ) (* version.ParsedSemVer , error ) {
345
380
vList , err := aac .GetVersions (ctx )
346
381
if err != nil {
347
382
return nil , err
@@ -355,7 +390,7 @@ func (aac ArtifactAPIClient) GetLatestSnapshotVersion(ctx context.Context, log l
355
390
for _ , v := range vList .Versions {
356
391
pv , err := version .ParseVersion (v )
357
392
if err != nil {
358
- log .Logf ("invalid version retrieved from artifact API: %q" , v )
393
+ aac . logger .Logf ("invalid version retrieved from artifact API: %q" , v )
359
394
return nil , ErrInvalidVersionRetrieved
360
395
}
361
396
sortedParsedVersions = append (sortedParsedVersions , pv )
0 commit comments