Skip to content

Commit 202236e

Browse files
committed
Improve error reporting when downloading agent configs
1 parent 79f9a5e commit 202236e

File tree

1 file changed

+53
-39
lines changed

1 file changed

+53
-39
lines changed

farcaster-go/config/config.go

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -106,32 +106,47 @@ func NewFarcasterConfig(token string, apiURLs []string, logger *zap.SugaredLogge
106106

107107
// Load fetches and parses the agent configuration from Probely's API.
108108
func (c *FarcasterConfig) Load(mustResolve bool) error {
109-
var err error
109+
type fetchResult struct {
110+
url string
111+
data []byte
112+
headers http.Header
113+
err error
114+
}
110115

111-
// Fetch the config using the API.
112-
var data []byte
116+
// Try each API URL, collecting diagnostics to report if all attempts fail.
117+
var results []fetchResult
113118
for _, url := range c.apiURLs {
114-
if data, err = c.fetch(url); err == nil {
115-
c.log.Infof("Fetched configuration from %s", url)
116-
break
119+
data, headers, err := c.fetch(url)
120+
if err != nil {
121+
results = append(results, fetchResult{
122+
url: url,
123+
data: data,
124+
headers: headers,
125+
err: err,
126+
})
127+
continue
117128
}
118-
}
119-
if err != nil {
120-
c.log.Errorf("Error fetching configuration. Tried: %s", strings.Join(c.apiURLs, ", "))
121-
return fmt.Errorf("could not fetch configuration: %s", err)
122-
}
123129

124-
// Decrypt the keys and build the raw configuration files.
125-
if err = c.build(data); err != nil {
126-
return fmt.Errorf("could not build configuration: %s", err)
130+
c.log.Infof("Fetched configuration from %s", url)
131+
if err = c.build(data); err != nil {
132+
return fmt.Errorf("could not build configuration: %w", err)
133+
}
134+
if err = c.parse(mustResolve); err != nil {
135+
return fmt.Errorf("could not parse configuration: %w", err)
136+
}
137+
return nil
127138
}
128139

129-
// Parse the configuration files.
130-
if err = c.parse(mustResolve); err != nil {
131-
return fmt.Errorf("could not parse configuration: %s", err)
140+
// All attempts failed. Log diagnostics for each failure.
141+
for _, result := range results {
142+
c.log.Errorf("Failed to fetch configuration from %s: %v", result.url, result.err)
143+
for k, v := range result.headers {
144+
c.log.Debugf(" %s: %s", k, strings.Join(v, ", "))
145+
}
146+
c.log.Debugf("Response body from %s: %s", result.url, result.data)
132147
}
133148

134-
return nil
149+
return fmt.Errorf("could not fetch configuration from any of %d URL(s)", len(c.apiURLs))
135150
}
136151

137152
func (c *FarcasterConfig) parse(mustResolve bool) error {
@@ -315,52 +330,51 @@ func (c *FarcasterConfig) getHTTPClient(timeout time.Duration) *http.Client {
315330
}
316331
}
317332

318-
// Fetch the configuration from the API.
319-
func (c *FarcasterConfig) fetch(url string) ([]byte, error) {
320-
var data []byte
321-
var err error
333+
// fetch returns the agent configuration along with response headers.
334+
// Headers are returned even on error to aid in troubleshooting network issues.
335+
func (c *FarcasterConfig) fetch(url string) (data []byte, headers http.Header, err error) {
336+
var tokenData []byte
322337

323338
// Create a public token. A public token is an identifier that allows us
324339
// to fetch the configuration for this agent.
325-
if data, err = base58.Decode(c.token); err != nil {
326-
return nil, err
340+
if tokenData, err = base58.Decode(c.token); err != nil {
341+
return nil, nil, err
327342
}
328-
cksum := sha256.Sum256(data)
343+
cksum := sha256.Sum256(tokenData)
329344
pubToken := base58.Encode(cksum[:])
330345

331-
// Prepare the request.
332346
u := fmt.Sprintf("%s/scanning-agents/%s/config-files/", url, pubToken)
333347
req, err := http.NewRequest("GET", u, nil)
334348
if err != nil {
335-
return nil, err
349+
return nil, nil, err
336350
}
337351

338-
// Set the user agent.
339352
userAgent := fmt.Sprintf("%s/%s (%s %s)", settings.Name, settings.Version, runtime.GOOS, runtime.GOARCH)
340353
req.Header.Set("User-Agent", userAgent)
341354

342-
// Send the request.
343355
client := c.getHTTPClient(defaultTimeout)
344356
resp, err := client.Do(req)
345357
if err != nil {
346-
return nil, err
358+
return nil, nil, err
347359
}
348360
defer resp.Body.Close()
349361

350-
// Check the response.
362+
headers = resp.Header.Clone()
363+
364+
// Read response body before checking status to capture error pages.
365+
data, err = io.ReadAll(resp.Body)
366+
if err != nil {
367+
return data, headers, fmt.Errorf("could not read response body: %w", err)
368+
}
369+
351370
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
352371
if resp.StatusCode == 404 {
353-
return nil, fmt.Errorf("agent token not found")
372+
return data, headers, fmt.Errorf("agent token not found (HTTP %d)", resp.StatusCode)
354373
}
355-
return nil, fmt.Errorf("server response code: %d", resp.StatusCode)
374+
return data, headers, fmt.Errorf("HTTP %d", resp.StatusCode)
356375
}
357376

358-
// Read the response body.
359-
if data, err = io.ReadAll(resp.Body); err != nil {
360-
return nil, fmt.Errorf("could not download config: %s", err)
361-
}
362-
363-
return data, nil
377+
return data, headers, nil
364378
}
365379

366380
// Decrypt configuration secrets, such as private keys.

0 commit comments

Comments
 (0)