A hold answered keep on the phone could not be changed: its ask ended with the answer, nothing listed it, and a kept hold is never lifted by itself. The view lists every hold with its node, cause and answer (mesh-delivery-cli holds) and answers the standing hold ask with hold-release or hold-keep through its one write; the teller takes a later view answer that changes a hold's option into the answer of that asking, never an older one.
134 lines
4.0 KiB
Go
134 lines
4.0 KiB
Go
// Command mesh-delivery-cli is the operator's command for the delivery view (novox/hq ADR 0429 §3). It asks the
|
|
// view's local socket for the rows and prints them; it holds no credential. `answer <ask key> <option> [words]` asks the view
|
|
// to write the operator's answer to an ask that needs no proof of who answered (the view refuses the merge's Approve). `holds`
|
|
// lists every fingerprint the assigner holds with its node, cause and answer; `hold-release <module> <fingerprint> [words]` and
|
|
// `hold-keep <module> <fingerprint> [words]` answer its hold ask, again when the operator changes their mind (issue 1906).
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"text/tabwriter"
|
|
|
|
"mesh-delivery-view/internal/view"
|
|
)
|
|
|
|
const defaultSocket = "/var/lib/mesh-delivery-view-cli/cli.sock"
|
|
|
|
func main() {
|
|
socket := os.Getenv("MESH_DELIVERY_SOCKET")
|
|
if socket == "" {
|
|
socket = defaultSocket
|
|
}
|
|
os.Exit(run(os.Args[1:], socket, os.Stdout, os.Stderr))
|
|
}
|
|
|
|
func run(args []string, socket string, out, errOut io.Writer) int {
|
|
fs := flag.NewFlagSet("mesh-delivery-cli", flag.ContinueOnError)
|
|
fs.SetOutput(errOut)
|
|
asJSON := fs.Bool("json", false, "print the rows as JSON")
|
|
if err := fs.Parse(args); err != nil {
|
|
return 2
|
|
}
|
|
if fs.NArg() > 0 && fs.Arg(0) == "holds" {
|
|
return holds(socket, *asJSON, out, errOut)
|
|
}
|
|
if fs.NArg() > 0 && (fs.Arg(0) == "hold-release" || fs.Arg(0) == "hold-keep") {
|
|
if fs.NArg() < 3 {
|
|
fmt.Fprintf(errOut, "mesh-delivery-cli %s <module> <fingerprint> [words]\n", fs.Arg(0))
|
|
return 2
|
|
}
|
|
option := strings.TrimPrefix(fs.Arg(0), "hold-")
|
|
r, err := view.AnswerHoldOn(socket, fs.Arg(1), fs.Arg(2), option, strings.Join(fs.Args()[3:], " "))
|
|
if err != nil {
|
|
fmt.Fprintln(errOut, "mesh-delivery-cli:", err)
|
|
return 1
|
|
}
|
|
fmt.Fprintf(out, "answered the hold of %s at %s with %s; the teller fills the ask from it and the assigner reads it\n", r.Module, r.FP, option)
|
|
return 0
|
|
}
|
|
if fs.NArg() > 0 {
|
|
if fs.Arg(0) != "answer" || fs.NArg() < 3 {
|
|
fmt.Fprintln(errOut, "mesh-delivery-cli shows the rows, or answers: mesh-delivery-cli answer <ask key> <option> [words]; holds; hold-release|hold-keep <module> <fingerprint> [words]")
|
|
return 2
|
|
}
|
|
key, option := fs.Arg(1), fs.Arg(2)
|
|
if err := view.AnswerAsk(socket, key, option, strings.Join(fs.Args()[3:], " ")); err != nil {
|
|
fmt.Fprintln(errOut, "mesh-delivery-cli:", err)
|
|
return 1
|
|
}
|
|
fmt.Fprintf(out, "answered %s with %s; the teller fills the ask from it\n", key, option)
|
|
return 0
|
|
}
|
|
s, err := view.Ask(socket)
|
|
if err != nil {
|
|
fmt.Fprintln(errOut, "mesh-delivery-cli:", err)
|
|
return 1
|
|
}
|
|
if *asJSON {
|
|
enc := json.NewEncoder(out)
|
|
enc.SetIndent("", " ")
|
|
if err := enc.Encode(s); err != nil {
|
|
fmt.Fprintln(errOut, "mesh-delivery-cli:", err)
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
view.Table(out, s)
|
|
return 0
|
|
}
|
|
|
|
// holds prints every hold: module, fingerprint, node, since, answer, words.
|
|
func holds(socket string, asJSON bool, out, errOut io.Writer) int {
|
|
rows, err := view.AskHolds(socket)
|
|
if err != nil {
|
|
fmt.Fprintln(errOut, "mesh-delivery-cli:", err)
|
|
return 1
|
|
}
|
|
if asJSON {
|
|
enc := json.NewEncoder(out)
|
|
enc.SetIndent("", " ")
|
|
if err := enc.Encode(rows); err != nil {
|
|
fmt.Fprintln(errOut, "mesh-delivery-cli:", err)
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
if len(rows) == 0 {
|
|
fmt.Fprintln(out, "the assigner holds nothing")
|
|
return 0
|
|
}
|
|
tw := tabwriter.NewWriter(out, 0, 2, 2, ' ', 0)
|
|
fmt.Fprintln(tw, "MODULE\tFINGERPRINT\tNODE\tSINCE\tANSWER\tWORDS")
|
|
for _, r := range rows {
|
|
answer := r.Answer
|
|
switch {
|
|
case r.AskKey == "":
|
|
answer = "no ask"
|
|
case r.Pending != "":
|
|
answer = r.Pending + " (from the view, the teller fills it)"
|
|
case answer == "":
|
|
answer = "not answered"
|
|
}
|
|
fp := strings.TrimPrefix(r.FP, "src2:")
|
|
if len(fp) > 12 {
|
|
fp = fp[:12]
|
|
}
|
|
since := ""
|
|
if !r.Since.IsZero() {
|
|
since = r.Since.Local().Format("2006-01-02 15:04")
|
|
}
|
|
words := r.Words
|
|
if r.Error != "" {
|
|
words = "unreadable: " + r.Error
|
|
}
|
|
fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%s\n", r.Module, fp, r.Node, since, answer, words)
|
|
}
|
|
tw.Flush()
|
|
return 0
|
|
}
|