- Any failure of a request the link may have delivered now says the line may have run; "nothing ran" is said only of a request the link knows it never asked (no link open, nobody listening: link.ErrNotAsked). - A test holds that a connection saying nothing is answered within its deadline, set at accept, and gives its slot back. - The README says the socket refuses a process outside the machine's own namespaces, so flatpak, toolbox and distrobox shells are refused.
93 lines
3.3 KiB
Go
93 lines
3.3 KiB
Go
package meshcli
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/cli"
|
|
"github.com/novox/mesh-host/internal/link"
|
|
)
|
|
|
|
// The controller answers a line as it answers every call (novox/hq ADR 0272, review): in an envelope, `result` or
|
|
// `error`, and within its window for an answer — so a line still running is answered `running` with its call, and
|
|
// followed by the same account on the same node until it ends. Its side holds the same field names (mesh-controller
|
|
// internal/link, the mesh-cli field names' test).
|
|
|
|
// ErrNotAsked is the link's: a request it knows it never delivered.
|
|
var ErrNotAsked = link.ErrNotAsked
|
|
|
|
// AskFunc asks the controller one request on this node's subject and returns its reply.
|
|
type AskFunc func(ctx context.Context, body []byte) ([]byte, error)
|
|
|
|
// followEvery is how often a line still running is asked about again.
|
|
var followEvery = 2 * time.Second
|
|
|
|
// envelope is a call's answer: its result, or its error.
|
|
type envelope struct {
|
|
Result json.RawMessage `json:"result"`
|
|
Error string `json:"error"`
|
|
}
|
|
|
|
// runningResult is what a call still running answers in its result.
|
|
type runningResult struct {
|
|
Running bool `json:"running"`
|
|
Call string `json:"call"`
|
|
}
|
|
|
|
// AskTheController asks the line and follows it to its answer, within ctx. What it says is one of three things,
|
|
// never confused: what the line printed; that nothing ran, and why (the request was never taken); or that what came
|
|
// of it is not known — it may have run — naming the call to look at when one is known.
|
|
func AskTheController(ctx context.Context, ask AskFunc, asked Asked) cli.Answer {
|
|
body, _ := json.Marshal(asked)
|
|
reply, err := ask(ctx, body)
|
|
if err != nil {
|
|
// **Only a request the link knows it never asked ran nothing**: no link open, nobody listening. Any other
|
|
// failure may come after the request was delivered, and the line may have run (review of ADR 0272).
|
|
if errors.Is(err, ErrNotAsked) {
|
|
return cli.Refusal("the controller was not reached, so nothing ran: " + err.Error())
|
|
}
|
|
return cli.NotKnown("", "no answer from the controller ("+err.Error()+")")
|
|
}
|
|
call := ""
|
|
for {
|
|
var env envelope
|
|
if err := json.Unmarshal(reply, &env); err != nil {
|
|
return cli.NotKnown(call, "the controller's answer could not be read")
|
|
}
|
|
if env.Error != "" {
|
|
return cli.NotKnown(call, "the controller answered "+env.Error)
|
|
}
|
|
var r runningResult
|
|
_ = json.Unmarshal(env.Result, &r)
|
|
if !r.Running {
|
|
var a cli.Answer
|
|
if err := json.Unmarshal(env.Result, &a); err != nil {
|
|
return cli.NotKnown(call, "the controller's answer could not be read")
|
|
}
|
|
return a
|
|
}
|
|
if r.Call != "" {
|
|
call = r.Call
|
|
}
|
|
if call == "" {
|
|
return cli.NotKnown("", "the controller says the line is running and names no call to follow")
|
|
}
|
|
// Asked again until it ends, through a link that may drop and come back: a failed follow is tried
|
|
// again, and only ctx ending stops it.
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return cli.NotKnown(call, fmt.Sprintf("no answer within the time mesh-cli waits (%s)", cli.AskWithin))
|
|
case <-time.After(followEvery):
|
|
}
|
|
body, _ := json.Marshal(Asked{Follow: call, Account: asked.Account, UID: asked.UID})
|
|
if reply, err = ask(ctx, body); err == nil {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|