package main import ( "context" "encoding/json" "errors" "flag" "fmt" "os" "time" "github.com/novox/mesh-controller/internal/link" ) // ask calls one of a module's tools, through the control plane's own broker connection. // // A module serves tools under an account scoped to exactly that (novox/hq ADR 0047), and nothing // else in the mesh held an account that could ask one — not an operator at a terminal, not an agent // acting for one (novox/hq 04-ISSUES/049). The control plane does, so it is the way in: one // process, one connection, one place a question can be seen to have been asked (ADR 0095). func askCommand(ctx context.Context, args []string) error { positionals, flags := split(args) set := flag.NewFlagSet("ask", flag.ContinueOnError) wait := set.Duration("wait", 60*time.Second, "how long to wait for the module's answer") if err := set.Parse(flags); err != nil { return err } if len(positionals) < 2 || len(positionals) > 3 { return errors.New("ask [json arguments] [--wait 60s]") } module, tool := positionals[0], positionals[1] var arguments json.RawMessage if len(positionals) == 3 { if !json.Valid([]byte(positionals[2])) { return fmt.Errorf("the arguments are not JSON: %s", positionals[2]) } arguments = json.RawMessage(positionals[2]) } server, err := connectLink(ctx, nil, nil, nil) if err != nil { return err } defer server.Close() answer, err := link.Ask(ctx, server.Bus(), module, tool, arguments, *wait) if err != nil { return err } // The answer as the module gave it, to standard output, for a person or a program. A tool // that answered with an error has still answered: printed the same way, and the exit status // says which. body, err := json.Marshal(answer) if err != nil { return err } fmt.Fprintln(os.Stdout, string(body)) if answer.Error != "" { return fmt.Errorf("%s.%s answered with an error: %s", module, tool, answer.Error) } return nil }