mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-14 01:19:09 +02:00
108 lines
3.7 KiB
Go
108 lines
3.7 KiB
Go
package modeldiscovery
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"syscall"
|
|
)
|
|
|
|
// Fetch serves two callers with different needs: the model picker, which only
|
|
// needs to know it failed, and the provider credential check, which has to
|
|
// tell an operator whether the URL or the key is at fault. Each failure
|
|
// carries a type so the second does not have to branch on a message.
|
|
|
|
// VendorStatusError reports a listing answered with something other than 200.
|
|
// Only the vendor's own code separates a refused credential (401, 403) from a
|
|
// URL that does not serve this API (404, 405) from an unwell vendor (5xx).
|
|
type VendorStatusError struct {
|
|
Provider string
|
|
Status int
|
|
}
|
|
|
|
func (e *VendorStatusError) Error() string {
|
|
return fmt.Sprintf("%s returned %d for its model listing", e.Provider, e.Status)
|
|
}
|
|
|
|
// UnreachableError reports that the request never reached the vendor: the
|
|
// name did not resolve, the connection was refused, TLS failed, or it timed
|
|
// out. Nothing was authenticated, so only the URL is implicated.
|
|
type UnreachableError struct {
|
|
Provider string
|
|
Err error
|
|
}
|
|
|
|
func (e *UnreachableError) Error() string {
|
|
return fmt.Sprintf("reach %s: %v", e.Provider, e.Err)
|
|
}
|
|
|
|
func (e *UnreachableError) Unwrap() error { return e.Err }
|
|
|
|
// Reason names the transport failure in words an operator can act on: a wrong
|
|
// port and a wrong hostname fail differently and are worth telling apart.
|
|
// Empty means unrecognised, and the caller should say only that the host could
|
|
// not be reached rather than paste a Go error into the UI.
|
|
func (e *UnreachableError) Reason() string {
|
|
err := e.Err
|
|
|
|
var dns *net.DNSError
|
|
if errors.As(err, &dns) {
|
|
if dns.IsNotFound {
|
|
return "no such host"
|
|
}
|
|
return "dns lookup failed"
|
|
}
|
|
|
|
// Timeouts are checked before the syscall cases: a dial that times out is
|
|
// reported as a net.OpError wrapping a timeout, and the operator needs to
|
|
// hear "timed out" rather than the syscall underneath it.
|
|
if errors.Is(err, context.DeadlineExceeded) || errors.Is(err, os.ErrDeadlineExceeded) {
|
|
return "connection timed out"
|
|
}
|
|
var netErr net.Error
|
|
if errors.As(err, &netErr) && netErr.Timeout() {
|
|
return "connection timed out"
|
|
}
|
|
|
|
if errors.Is(err, syscall.ECONNREFUSED) {
|
|
return "connection refused"
|
|
}
|
|
if errors.Is(err, syscall.EHOSTUNREACH) || errors.Is(err, syscall.ENETUNREACH) {
|
|
return "host unreachable"
|
|
}
|
|
|
|
var certErr *tls.CertificateVerificationError
|
|
if errors.As(err, &certErr) {
|
|
return "tls certificate not trusted"
|
|
}
|
|
var recordErr tls.RecordHeaderError
|
|
if errors.As(err, &recordErr) {
|
|
return "not a tls endpoint"
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// ErrUnparseableListing marks a 200 whose body is not a listing in the shape
|
|
// the catalog declared. Distinct from a status refusal: the host answered and
|
|
// authenticated fine, it is just not the API — a login page, say.
|
|
var ErrUnparseableListing = errors.New("response is not a model listing")
|
|
|
|
// ErrNoDiscoveryHost marks a provider whose listing host cannot be derived
|
|
// from the record: Bedrock's control-plane host comes from the region in the
|
|
// upstream, so a proxied endpoint leaves nowhere to send it, and inventing one
|
|
// would spend the credential somewhere never configured.
|
|
//
|
|
// Wraps ErrInvalidRequest so the discovery endpoint still answers 400, while a
|
|
// credential check can read it as "cannot be checked" rather than "broken".
|
|
var ErrNoDiscoveryHost = errors.New("provider has no derivable discovery host")
|
|
|
|
// ErrPrivateHost marks an upstream resolving somewhere management will not
|
|
// dial. A self-hosted endpoint on a private network is a legitimate provider
|
|
// the proxy reaches through the tunnel, so this means the check cannot run,
|
|
// not that the record is wrong.
|
|
var ErrPrivateHost = errors.New("discovery host is not publicly routable")
|