diff --git a/internal/cli/admin.go b/internal/cli/admin.go index b0c7f0e..9c854dd 100644 --- a/internal/cli/admin.go +++ b/internal/cli/admin.go @@ -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 ` and `config get `. +// runConfig handles `config ` 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 [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 ") + 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 ") 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 } } diff --git a/internal/cli/admin_test.go b/internal/cli/admin_test.go index e836c36..929ae7a 100644 --- a/internal/cli/admin_test.go +++ b/internal/cli/admin_test.go @@ -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) } }