Docker agent: add arch-aware self-update download

Refs #526
This commit is contained in:
rcourtman 2025-10-16 08:43:59 +00:00
parent 61b492e93b
commit 79dc620b34

View file

@ -12,6 +12,7 @@ import (
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
"runtime"
"strings" "strings"
"syscall" "syscall"
"time" "time"
@ -825,6 +826,34 @@ func (a *Agent) checkForUpdates(ctx context.Context) {
a.logger.Info().Msg("Agent updated successfully, restarting...") a.logger.Info().Msg("Agent updated successfully, restarting...")
} }
func determineSelfUpdateArch() string {
switch runtime.GOARCH {
case "amd64":
return "linux-amd64"
case "arm64":
return "linux-arm64"
case "arm":
return "linux-armv7"
}
out, err := exec.Command("uname", "-m").Output()
if err != nil {
return ""
}
normalized := strings.ToLower(strings.TrimSpace(string(out)))
switch normalized {
case "x86_64", "amd64":
return "linux-amd64"
case "aarch64", "arm64":
return "linux-arm64"
case "armv7l", "armhf", "armv7":
return "linux-armv7"
default:
return ""
}
}
// selfUpdate downloads the new agent binary and replaces the current one // selfUpdate downloads the new agent binary and replaces the current one
func (a *Agent) selfUpdate(ctx context.Context) error { func (a *Agent) selfUpdate(ctx context.Context) error {
target := a.primaryTarget() target := a.primaryTarget()
@ -838,29 +867,70 @@ func (a *Agent) selfUpdate(ctx context.Context) error {
return fmt.Errorf("failed to get executable path: %w", err) return fmt.Errorf("failed to get executable path: %w", err)
} }
// Download new binary downloadBase := strings.TrimRight(target.URL, "/") + "/download/pulse-docker-agent"
url := fmt.Sprintf("%s/download/pulse-docker-agent", target.URL) archParam := determineSelfUpdateArch()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil { type downloadCandidate struct {
return fmt.Errorf("failed to create download request: %w", err) url string
arch string
} }
if target.Token != "" { candidates := make([]downloadCandidate, 0, 2)
req.Header.Set("X-API-Token", target.Token) if archParam != "" {
req.Header.Set("Authorization", "Bearer "+target.Token) candidates = append(candidates, downloadCandidate{
url: fmt.Sprintf("%s?arch=%s", downloadBase, archParam),
arch: archParam,
})
} }
candidates = append(candidates, downloadCandidate{url: downloadBase})
client := a.httpClientFor(target) client := a.httpClientFor(target)
resp, err := client.Do(req) var resp *http.Response
if err != nil { var lastErr error
return fmt.Errorf("failed to download new binary: %w", err)
for _, candidate := range candidates {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, candidate.url, nil)
if err != nil {
lastErr = fmt.Errorf("failed to create download request: %w", err)
continue
}
if target.Token != "" {
req.Header.Set("X-API-Token", target.Token)
req.Header.Set("Authorization", "Bearer "+target.Token)
}
response, err := client.Do(req)
if err != nil {
lastErr = fmt.Errorf("failed to download new binary: %w", err)
continue
}
if response.StatusCode != http.StatusOK {
lastErr = fmt.Errorf("download failed with status: %s", response.Status)
response.Body.Close()
continue
}
resp = response
if candidate.arch != "" {
a.logger.Debug().
Str("arch", candidate.arch).
Msg("Self-update: downloaded architecture-specific agent binary")
} else if archParam != "" {
a.logger.Debug().Msg("Self-update: falling back to server default agent binary")
}
break
}
if resp == nil {
if lastErr == nil {
lastErr = errors.New("failed to download new binary")
}
return lastErr
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("download failed with status: %s", resp.Status)
}
// Create temporary file // Create temporary file
tmpFile, err := os.CreateTemp("", "pulse-docker-agent-*.tmp") tmpFile, err := os.CreateTemp("", "pulse-docker-agent-*.tmp")
if err != nil { if err != nil {