feat(cli): positional config grammar with registry + config list
This commit is contained in:
+45
-22
@@ -4,7 +4,6 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"git.dcglab.co.uk/steve/emcli/internal/crypto"
|
||||
"git.dcglab.co.uk/steve/emcli/internal/store"
|
||||
@@ -231,7 +230,7 @@ func auditList(st *store.Store, account string, limit int, out io.Writer) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// runConfig handles `config set <key> <value>` and `config get <key>`.
|
||||
// runConfig handles `config <list|get|set>` against the settings registry.
|
||||
func runConfig(args []string, role store.Role, out, errOut io.Writer) int {
|
||||
if len(args) == 0 || helpRequested(args[0]) {
|
||||
printCmdUsage(out, "config")
|
||||
@@ -240,11 +239,8 @@ func runConfig(args []string, role store.Role, out, errOut io.Writer) int {
|
||||
}
|
||||
return 2
|
||||
}
|
||||
if len(args) < 2 {
|
||||
fmt.Fprintln(errOut, "usage: emcli config <set|get> <key> [value]")
|
||||
return 2
|
||||
}
|
||||
sub, key := args[0], args[1]
|
||||
sub := normalizeVerb(args[0])
|
||||
rest := args[1:]
|
||||
st, err := openStore(role)
|
||||
if err != nil {
|
||||
fmt.Fprintf(errOut, "emcli: %v\n", err)
|
||||
@@ -253,16 +249,51 @@ func runConfig(args []string, role store.Role, out, errOut io.Writer) int {
|
||||
defer st.Close()
|
||||
|
||||
switch sub {
|
||||
case "list":
|
||||
if len(rest) > 0 {
|
||||
fmt.Fprintf(errOut, "unexpected argument %q\n", rest[0])
|
||||
return 2
|
||||
}
|
||||
fmt.Fprintf(out, "%-22s %-8s %s\n", "KEY", "VALUE", "DESCRIPTION")
|
||||
for _, k := range settingKeys() {
|
||||
v, err := st.GetSetting(k)
|
||||
if err != nil {
|
||||
v = "(unset)"
|
||||
}
|
||||
fmt.Fprintf(out, "%-22s %-8s %s\n", k, v, settingsRegistry[k].desc)
|
||||
}
|
||||
return 0
|
||||
case "get":
|
||||
if len(rest) != 1 {
|
||||
fmt.Fprintln(errOut, "usage: emcli config get <key>")
|
||||
return 2
|
||||
}
|
||||
key := rest[0]
|
||||
if _, ok := settingsRegistry[key]; !ok {
|
||||
fmt.Fprintf(errOut, "unknown setting %q (see: emcli config list)\n", key)
|
||||
return 2
|
||||
}
|
||||
v, err := st.GetSetting(key)
|
||||
if err != nil {
|
||||
fmt.Fprintf(errOut, "config get: %s not set\n", key)
|
||||
return 1
|
||||
}
|
||||
fmt.Fprintf(out, "%s = %s\n", key, v)
|
||||
return 0
|
||||
case "set":
|
||||
if len(args) < 3 {
|
||||
if len(rest) != 2 {
|
||||
fmt.Fprintln(errOut, "usage: emcli config set <key> <value>")
|
||||
return 2
|
||||
}
|
||||
value := args[2]
|
||||
if key == "audit_retention_days" {
|
||||
n, err := strconv.Atoi(value)
|
||||
if err != nil || n < 0 {
|
||||
fmt.Fprintf(errOut, "audit_retention_days must be an integer >= 0, got %q\n", value)
|
||||
key, value := rest[0], rest[1]
|
||||
def, ok := settingsRegistry[key]
|
||||
if !ok {
|
||||
fmt.Fprintf(errOut, "unknown setting %q (see: emcli config list)\n", key)
|
||||
return 2
|
||||
}
|
||||
if def.validate != nil {
|
||||
if err := def.validate(value); err != nil {
|
||||
fmt.Fprintf(errOut, "%s %v\n", key, err)
|
||||
return 2
|
||||
}
|
||||
}
|
||||
@@ -272,16 +303,8 @@ func runConfig(args []string, role store.Role, out, errOut io.Writer) int {
|
||||
}
|
||||
fmt.Fprintf(out, "%s = %s\n", key, value)
|
||||
return 0
|
||||
case "get":
|
||||
v, err := st.GetSetting(key)
|
||||
if err != nil {
|
||||
fmt.Fprintf(errOut, "config get: %s not set\n", key)
|
||||
return 1
|
||||
}
|
||||
fmt.Fprintf(out, "%s = %s\n", key, v)
|
||||
return 0
|
||||
default:
|
||||
fmt.Fprintf(errOut, "unknown config subcommand %q\n", sub)
|
||||
fmt.Fprintf(errOut, "unknown config subcommand %q (want list|get|set)\n", sub)
|
||||
return 2
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,11 +52,33 @@ func TestConfigSetGet(t *testing.T) {
|
||||
|
||||
func TestConfigSetRejectsBadRetention(t *testing.T) {
|
||||
adminEnv(t)
|
||||
if code, _, _ := run(t, "config", "set", "audit_retention_days", "-5"); code == 0 {
|
||||
t.Fatal("negative retention must be rejected")
|
||||
if code, _, _ := run(t, "config", "set", "audit_retention_days", "-5"); code != 2 {
|
||||
t.Fatal("negative retention must be a usage error")
|
||||
}
|
||||
if code, _, _ := run(t, "config", "set", "audit_retention_days", "abc"); code == 0 {
|
||||
t.Fatal("non-integer retention must be rejected")
|
||||
if code, _, _ := run(t, "config", "set", "audit_retention_days", "abc"); code != 2 {
|
||||
t.Fatal("non-integer retention must be a usage error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigRejectsUnknownKey(t *testing.T) {
|
||||
adminEnv(t)
|
||||
if code, _, e := run(t, "config", "set", "bogus", "1"); code != 2 || !strings.Contains(e, "unknown setting") {
|
||||
t.Fatalf("set unknown key: code=%d err=%q", code, e)
|
||||
}
|
||||
if code, _, e := run(t, "config", "get", "bogus"); code != 2 || !strings.Contains(e, "unknown setting") {
|
||||
t.Fatalf("get unknown key: code=%d err=%q", code, e)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigList(t *testing.T) {
|
||||
adminEnv(t)
|
||||
run(t, "config", "set", "audit_retention_days", "42")
|
||||
code, out, _ := run(t, "config", "ls") // alias
|
||||
if code != 0 {
|
||||
t.Fatalf("config ls exit=%d", code)
|
||||
}
|
||||
if !strings.Contains(out, "audit_retention_days") || !strings.Contains(out, "42") || !strings.Contains(out, "KEY") {
|
||||
t.Fatalf("config list output wrong:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user