The controller's tools can set an assignment's settings
Per-machine and mesh-wide settings could be set only from the controller's command line. The settings verb runs settings set|clear, passing the values inline, which the command now accepts as well as a file (novox/hq issue 198).
This commit is contained in:
@@ -352,10 +352,14 @@ func settingsCommand(ctx context.Context, args []string) error {
|
||||
switch args[0] {
|
||||
case "set":
|
||||
if len(positionals) != 2 {
|
||||
return errors.New("settings set <module> <settings.json> [--node <node>]")
|
||||
return errors.New("settings set <module> <settings.json | {…}> [--node <node>]")
|
||||
}
|
||||
raw, err := os.ReadFile(positionals[1])
|
||||
if err != nil {
|
||||
// A file, or the values themselves when they begin with `{` — which is how the mesh's own
|
||||
// `settings` tool passes them, having no file to hand over (novox/hq issue 198).
|
||||
var raw []byte
|
||||
if strings.HasPrefix(strings.TrimSpace(positionals[1]), "{") {
|
||||
raw = []byte(positionals[1])
|
||||
} else if raw, err = os.ReadFile(positionals[1]); err != nil {
|
||||
return err
|
||||
}
|
||||
var values map[string]any
|
||||
|
||||
@@ -129,6 +129,25 @@ func argvFor(verb string, args map[string]any) ([]string, error) {
|
||||
// Half of either shape: the command says its usage, which names both shapes, and that is
|
||||
// the answer the caller needs.
|
||||
return []string{"rotate"}, nil
|
||||
case "settings":
|
||||
// `settings set|clear` at a shell (novox/hq issue 198). The values travel as an argument
|
||||
// because a tool has no file to hand the command; the command reads either.
|
||||
if err := need("module"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
argv := []string{"settings", "set", str("module")}
|
||||
switch {
|
||||
case str("clear") == "true":
|
||||
argv = []string{"settings", "clear", str("module")}
|
||||
case str("values") != "":
|
||||
argv = append(argv, str("values"))
|
||||
}
|
||||
// Neither values nor clear: the command says its usage, which names both, and that is the
|
||||
// answer the caller needs — the same as `rotate` given half of either shape.
|
||||
if n := str("node"); n != "" {
|
||||
argv = append(argv, "--node", n)
|
||||
}
|
||||
return argv, nil
|
||||
case "issue":
|
||||
// The same act as `module issue` at a shell (novox/hq design 25 §4): the account is minted
|
||||
// into the mesh's records and delivered at the machine's next push, which is the caller's to
|
||||
|
||||
@@ -73,6 +73,22 @@ func TestRotateTakesAProvisionOrAnOwnSecret(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// `settings` is `settings set|clear` at a shell, with the values passed inline (novox/hq issue 198).
|
||||
func TestSettingsSetsOrClearsALayer(t *testing.T) {
|
||||
argv, err := argvFor("settings", map[string]any{"module": "dnsmasq", "values": `{"a":1}`, "node": "ace"})
|
||||
if err != nil || strings.Join(argv, " ") != `settings set dnsmasq {"a":1} --node ace` {
|
||||
t.Fatalf("set on a machine: %v %v", argv, err)
|
||||
}
|
||||
argv, _ = argvFor("settings", map[string]any{"module": "dnsmasq", "clear": "true"})
|
||||
if strings.Join(argv, " ") != "settings clear dnsmasq" {
|
||||
t.Fatalf("clear for the mesh: %v", argv)
|
||||
}
|
||||
argv, _ = argvFor("settings", map[string]any{"module": "dnsmasq"})
|
||||
if strings.Join(argv, " ") != "settings set dnsmasq" {
|
||||
t.Fatalf("a set with no values falls to the command's usage: %v", argv)
|
||||
}
|
||||
}
|
||||
|
||||
// `issue` is `module issue` at a shell: the module and the machine, and nothing that would push. A
|
||||
// module's bus account was mintable only from the controller's command line, so an agent working
|
||||
// through the tools could not finish a rollout that gave a module one (novox/hq issue 191).
|
||||
|
||||
@@ -136,6 +136,15 @@ var ControllerVerbs = []Verb{
|
||||
"node": "the machine that runs the module",
|
||||
"module": "the module's name",
|
||||
}, []string{"node", "module"})},
|
||||
{Name: "settings", Description: "Set what an assignment is configured with: a module's settings for the whole mesh, " +
|
||||
"or for one machine. Replaces that layer whole — what it does not name, it no longer sets — and takes effect " +
|
||||
"at the next push. With clear, removes the layer and the module is back to what its definition says.",
|
||||
Input: schema(map[string]string{
|
||||
"module": "the module's name",
|
||||
"values": "the settings as a JSON object, for set",
|
||||
"node": "one machine; the whole mesh when absent",
|
||||
"clear": "\"true\" to remove the layer instead of setting it",
|
||||
}, []string{"module"})},
|
||||
{Name: "build", Description: "Have the build machine build a repository. Answers at once with the build's id: " +
|
||||
"`builds` with that id follows it line by line, and the module is registered when the outcome comes.",
|
||||
Input: schema(map[string]string{
|
||||
|
||||
Reference in New Issue
Block a user