Answer mesh-cli: the control-node's operator is the terminal, everyone else is not (hq ADR 0272)
The records send the operator to "the controller's terminal", and nothing reached it (hq issue 343). The serving controller now answers each node's mesh.control.<node>.cli, where only that node's engine may publish, with the account the engine read from the kernel. A line from the control-node's operator account runs as the terminal: a fresh process of this binary with no MESH_VERB, whatever the serving process carries. The same account on any other node, where agents may run as it, is an ordinary call: the generic command verb's refusals (one function now, so the two routes cannot drift) and MESH_VERB=mesh-cli, so a terminal-only change is refused with its reason. Any other account, root included, runs nothing. Servers are never run, and an answer over one bus message is cut and says so. The terminal-only refusals and the usage now name mesh-cli on the control-node as the way to the terminal.
This commit is contained in:
@@ -226,6 +226,30 @@ func quoteAll(xs []string) string {
|
||||
return strings.Join(q, ", ")
|
||||
}
|
||||
|
||||
// refusedAsTheGenericCommand is what the generic `command` verb refuses of a command line, and so what every
|
||||
// line asked through a verb's route refuses: the `command` verb's, and an ordinary line from mesh-cli (novox/hq ADR
|
||||
// 0272 §4). One function, so the two routes cannot drift apart.
|
||||
func refusedAsTheGenericCommand(argv []string) error {
|
||||
if len(argv) == 0 {
|
||||
return errors.New("command names no command")
|
||||
}
|
||||
// A layer is written through the settings verb, never the generic one (novox/hq issue 339): the
|
||||
// settings verb is where what a verb may not set is refused, and one route is one set of words.
|
||||
// The command refuses places and accesses through any verb as well; this says so before it runs.
|
||||
if argv[0] == "settings" && slices.ContainsFunc(argv[1:], func(w string) bool { return w == "set" || w == "clear" }) {
|
||||
return errors.New("settings are set and cleared through the settings verb, not the generic " +
|
||||
"command; and places and accesses only at the controller's terminal (novox/hq issue 339). " +
|
||||
"Nothing was done")
|
||||
}
|
||||
// The generic verb is no way round the hand-act log (novox/hq to-be 45 §7): a repair through
|
||||
// it says why, as it would through its own verb.
|
||||
if repair := repairingCommand(argv); repair != "" && !slices.ContainsFunc(argv, isWhyFlag) {
|
||||
return fmt.Errorf("%s is a repair done by hand, and says why: add --why <text> to the command "+
|
||||
"line (recorded in the hand-act log). Nothing was done", repair)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// commandLine composes the command. Every argument it reads is one it uses: a branch that reads an
|
||||
// argument and then drops it would pass it over, which is what the check after it exists to refuse.
|
||||
func (a *verbArguments) commandLine() ([]string, error) {
|
||||
@@ -242,22 +266,8 @@ func (a *verbArguments) commandLine() ([]string, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(argv) == 0 {
|
||||
return nil, errors.New("command names no command")
|
||||
}
|
||||
// A layer is written through the settings verb, never the generic one (novox/hq issue 339): the
|
||||
// settings verb is where what a verb may not set is refused, and one route is one set of words.
|
||||
// The command refuses places and accesses through any verb as well; this says so before it runs.
|
||||
if argv[0] == "settings" && slices.ContainsFunc(argv[1:], func(w string) bool { return w == "set" || w == "clear" }) {
|
||||
return nil, errors.New("settings are set and cleared through the settings verb, not the generic " +
|
||||
"command; and places and accesses only at the controller's terminal (novox/hq issue 339). " +
|
||||
"Nothing was done")
|
||||
}
|
||||
// The generic verb is no way round the hand-act log (novox/hq to-be 45 §7): a repair through
|
||||
// it says why, as it would through its own verb.
|
||||
if repair := repairingCommand(argv); repair != "" && !slices.ContainsFunc(argv, isWhyFlag) {
|
||||
return nil, fmt.Errorf("%s is a repair done by hand, and says why: add --why <text> to the command "+
|
||||
"line (recorded in the hand-act log). Nothing was done", repair)
|
||||
if err := refusedAsTheGenericCommand(argv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return argv, nil
|
||||
case "tools":
|
||||
@@ -911,6 +921,25 @@ func isWhyFlag(word string) bool {
|
||||
return word == "--why" || word == "-why" || strings.HasPrefix(word, "--why=") || strings.HasPrefix(word, "-why=")
|
||||
}
|
||||
|
||||
// commandEnvironment is the environment of a command line this controller runs for someone: its own — the
|
||||
// stores' credentials, the bus, the broker, everything a command run from a shell beside it would have, because it
|
||||
// is that — with who asked, and the verb it came through. An empty verb is the controller's terminal (novox/hq ADR
|
||||
// 0272 §4): no `MESH_VERB` at all, whatever this process was started with.
|
||||
func commandEnvironment(caller, verb string) []string {
|
||||
env := make([]string, 0, len(os.Environ())+2)
|
||||
for _, kv := range os.Environ() {
|
||||
if strings.HasPrefix(kv, verbVar+"=") || strings.HasPrefix(kv, link.CallerVar+"=") {
|
||||
continue
|
||||
}
|
||||
env = append(env, kv)
|
||||
}
|
||||
env = append(env, link.CallerVar+"="+caller)
|
||||
if verb != "" {
|
||||
env = append(env, verbVar+"="+verb)
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
// runVerb runs this binary with the given command line and gathers what it said.
|
||||
//
|
||||
// **This binary is the image this process runs, never the file at the path it started from**
|
||||
@@ -924,21 +953,17 @@ func runVerb(ctx context.Context, argv []string) (verbAnswer, error) {
|
||||
return verbAnswer{}, fmt.Errorf("%w: this controller is stopping and runs no new command", link.ErrHandingOver)
|
||||
}
|
||||
cmd := selfCommand(ctx, argv)
|
||||
// The same environment: the stores' credentials, the bus, the broker — everything a command run
|
||||
// from a shell in this container would have, because it is that.
|
||||
cmd.Env = os.Environ()
|
||||
// And who asked, so an act it does by hand is recorded as theirs (novox/hq to-be 45 §7).
|
||||
caller := link.CallerIn(ctx)
|
||||
if caller == "" {
|
||||
caller = "a seat call whose caller the bus did not name"
|
||||
}
|
||||
cmd.Env = append(cmd.Env, link.CallerVar+"="+caller+", through the "+catalogue.ControllerSeatName+" seat")
|
||||
// And which verb, so the connection it dials says so in the bus's list (novox/hq issue 327).
|
||||
verb, _ := ctx.Value(verbKey{}).(string)
|
||||
if verb == "" {
|
||||
verb = argv[0]
|
||||
}
|
||||
cmd.Env = append(cmd.Env, verbVar+"="+verb)
|
||||
cmd.Env = commandEnvironment(caller+", through the "+catalogue.ControllerSeatName+" seat", verb)
|
||||
// Two buffers, one answer. What the command *says* is both streams, in the order a person at
|
||||
// a shell would read them; what it *answers as data* is standard output alone — `status --json`
|
||||
// prints its warnings beside the document, and a JSON parsed from the two together parsed
|
||||
|
||||
Reference in New Issue
Block a user