package buildstep import ( "errors" "fmt" "strings" ) // Passing is a fault of the network between a build node and what it reaches (a name lookup, a timeout, a reset // connection, a forge's sshd refusing a burst, a store that does not answer), not of the source or the toolchain. A // build module wraps it, and the shared runner then writes the step again and settles its ask with a delayed nak, up to // AskRuns runs (ADR 0664 §1, §2). Any other error ends the step failed at once. type Passing struct{ Err error } func (p *Passing) Error() string { return p.Err.Error() } func (p *Passing) Unwrap() error { return p.Err } // IsPassing says err is, or wraps, a Passing. func IsPassing(err error) bool { var p *Passing return errors.As(err, &p) } // HolderSilentWords opens the words of a HolderSilent, so PassingCause reads them from a step's reason too. const HolderSilentWords = "the holder is silent" // HolderSilent is a seat verb that answered "no responders" while the assigner names a holder of the seat: its tool // runner is being replaced, a moment later it answers. It is a Passing fault (ADR 0664 §1); a seat nothing holds is not. type HolderSilent struct{ Seat, Holder, Node string } func (h *HolderSilent) Error() string { return fmt.Sprintf("%s: seat %s, held by %s on %s, answered no responders", HolderSilentWords, h.Seat, h.Holder, h.Node) } // Unwrap makes a HolderSilent a Passing. func (h *HolderSilent) Unwrap() error { return &Passing{Err: errors.New(h.Error())} } // passingWords are the words of a network that did not answer, lower-cased: go's and git's (a lookup, a timeout, a refused or // reset connection, ssh's key exchange cut by a forge's per-source penalty) and npm's error codes. "no responders" alone is not // one: it is the same for a seat nothing holds (HolderSilent is the case a holder is named). var passingWords = []string{"i/o timeout", "no such host", "temporary failure in name resolution", "connection refused", "connection reset", "tls handshake timeout", "network is unreachable", "no route to host", "lookup ", "kex_exchange_identification", "connection closed by", "could not resolve host", "client.timeout", "operation timed out", "connection timed out", "eai_again", "etimedout", "econnreset", "econnrefused", "enotfound", strings.ToLower(HolderSilentWords)} // PassingCause says words (a tool's output or a step's reason) are those of a transport fault. func PassingCause(words string) bool { words = strings.ToLower(words) for _, w := range passingWords { if strings.Contains(words, w) { return true } } return false } // TransportFaultWords open the reason of a step that ended failed after its runs met a transport fault (the asker says them, // "a transport fault on 3 runs (nodes): cause"): a reader tells that end from a fault of the source by them (ADR 0664 §3). const TransportFaultWords = "a transport fault on "