Files
wehub-resource-sync f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

89 lines
2.6 KiB
Go

package remotesync
import (
"context"
"errors"
"fmt"
"net"
"net/http"
"strconv"
"syscall"
)
// FailureSummary maps an HTTP remote sync error to a short,
// actionable message that is safe to surface through the API and
// CLI. Raw errors can embed the full remote URL and response
// bodies, so callers log the raw error locally and hand users this
// summary instead. Unrecognized errors collapse to the generic
// message rather than leaking their text.
func FailureSummary(err error) string {
const generic = "HTTP remote sync failed"
if err == nil {
return generic
}
var pending *PendingCleanupError
if errors.As(err, &pending) {
return "HTTP remote sync blocked: cleanup from an earlier sync " +
"still owns resources"
}
var statusErr *StatusError
if errors.As(err, &statusErr) {
// statusLabel derives the display text locally from the
// numeric code: the response status line (and Detail) are
// remote-controlled and must never reach the summary.
switch statusErr.Code {
case 401, 403:
return fmt.Sprintf(
"HTTP remote sync failed: remote daemon rejected the "+
"sync token (%s); the token for this host in "+
"[[remote_hosts]] must match the remote daemon's "+
"auth_token", statusLabel(statusErr.Code),
)
case 404:
return fmt.Sprintf(
"HTTP remote sync failed: remote daemon has no "+
"remote-sync endpoints (%s); upgrade agentsview on "+
"the remote host", statusLabel(statusErr.Code),
)
default:
return fmt.Sprintf(
"HTTP remote sync failed: remote daemon returned %s",
statusLabel(statusErr.Code),
)
}
}
if errors.Is(err, syscall.ECONNREFUSED) {
return "HTTP remote sync failed: connection refused; check " +
"that the remote daemon is running and bound to a " +
"reachable address (serve --host 0.0.0.0 or host in its " +
"config.toml), and that the url port matches"
}
var dnsErr *net.DNSError
if errors.As(err, &dnsErr) {
return "HTTP remote sync failed: cannot resolve the remote " +
"host name; check the url in this [[remote_hosts]] entry"
}
var netErr net.Error
if errors.Is(err, context.DeadlineExceeded) ||
(errors.As(err, &netErr) && netErr.Timeout()) {
return "HTTP remote sync failed: connection timed out; check " +
"that the remote host is reachable and the url is correct"
}
return generic
}
// statusLabel renders an HTTP status for user-facing messages using
// only the locally-known status text for the code, never the
// remote-supplied reason phrase.
func statusLabel(code int) string {
if text := http.StatusText(code); text != "" {
return fmt.Sprintf("%d %s", code, text)
}
return strconv.Itoa(code)
}