Add configuration path explanations
This commit is contained in:
@@ -23,16 +23,19 @@ func configCommand(args []string, stdout, stderr io.Writer) int {
|
||||
return ExitRuntimeError
|
||||
}
|
||||
mode := args[0]
|
||||
if mode != "validate" && mode != "show" && mode != "sources" {
|
||||
if mode != "validate" && mode != "show" && mode != "sources" && mode != "explain" {
|
||||
fmt.Fprintln(stderr, "config parse error: unknown configuration mode")
|
||||
return ExitRuntimeError
|
||||
}
|
||||
|
||||
fs := flag.NewFlagSet("config "+mode, flag.ContinueOnError)
|
||||
fs.SetOutput(io.Discard)
|
||||
var path, overlay string
|
||||
var path, overlay, pointer string
|
||||
registerConfigSelectionFlags(fs, &path, &overlay)
|
||||
registerConfigurationOverrides(fs)
|
||||
if mode == "explain" {
|
||||
fs.StringVar(&pointer, "path", "", "configuration path as a JSON Pointer")
|
||||
}
|
||||
if err := fs.Parse(args[1:]); err != nil {
|
||||
writeApplicationError(stderr, "config parse", err)
|
||||
return ExitRuntimeError
|
||||
@@ -41,6 +44,19 @@ func configCommand(args []string, stdout, stderr io.Writer) int {
|
||||
fmt.Fprintln(stderr, "config parse error: positional arguments are not accepted")
|
||||
return ExitRuntimeError
|
||||
}
|
||||
var explanationPath configkit.Path
|
||||
if mode == "explain" {
|
||||
if !flagWasSet(fs, "path") {
|
||||
fmt.Fprintln(stderr, "config parse error: --path is required")
|
||||
return ExitRuntimeError
|
||||
}
|
||||
var err error
|
||||
explanationPath, err = configkit.ParsePath(pointer)
|
||||
if err != nil {
|
||||
fmt.Fprintln(stderr, "config parse error: --path must be a JSON Pointer")
|
||||
return ExitRuntimeError
|
||||
}
|
||||
}
|
||||
|
||||
result, err := appconfig.Load(appconfig.LoadOptions{
|
||||
Path: path, Explicit: flagWasSet(fs, "config"), Overlay: overlay,
|
||||
@@ -66,6 +82,8 @@ func configCommand(args []string, stdout, stderr io.Writer) int {
|
||||
})
|
||||
}
|
||||
err = json.NewEncoder(stdout).Encode(sources)
|
||||
case "explain":
|
||||
err = result.WriteExplanation(stdout, explanationPath)
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Fprintf(stderr, "config output error: %v\n", err)
|
||||
|
||||
@@ -126,3 +126,82 @@ func TestConfigCommandFailuresDoNotProduceSuccessfulOutput(t *testing.T) {
|
||||
t.Fatalf("source writer error returned %d", code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigExplainUsesResolvedHistoryAndKeepsParametersVisible(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
if err := os.MkdirAll(filepath.Join(dir, "overlays"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
base := filepath.Join(dir, "config.yml")
|
||||
for path, body := range map[string]string{
|
||||
base: `prompt_dir: " ./prompts/../prompts "
|
||||
server:
|
||||
addr: :8080
|
||||
backends:
|
||||
"team/blue~1":
|
||||
endpoint: http://127.0.0.1:1/v1
|
||||
api_key_env: SCRIPTORIUM_EXPLAIN_KEY
|
||||
extra_params:
|
||||
"0": base-parameter
|
||||
`,
|
||||
filepath.Join(dir, "overlays", "local.yml"): `server:
|
||||
addr: :9090
|
||||
backends:
|
||||
"team/blue~1":
|
||||
extra_params:
|
||||
"0": overlay-parameter
|
||||
`,
|
||||
} {
|
||||
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
const keyValue = "synthetic-key-bytes-must-not-appear"
|
||||
t.Setenv("SCRIPTORIUM_EXPLAIN_KEY", keyValue)
|
||||
args := []string{"--config", base, "--config-overlay", "local", "--addr", ":9090"}
|
||||
for _, tc := range []struct {
|
||||
pointer string
|
||||
want []string
|
||||
}{
|
||||
{pointer: "/server/addr", want: []string{":8080", ":9090", "--addr"}},
|
||||
{pointer: "/prompt_dir", want: []string{"normalized application path", "prompts"}},
|
||||
{pointer: "/backends/team~1blue~01/extra_params/0", want: []string{"base-parameter", "overlay-parameter"}},
|
||||
{pointer: "/backends/team~1blue~01/api_key_env", want: []string{"SCRIPTORIUM_EXPLAIN_KEY"}},
|
||||
{pointer: "", want: []string{"base-parameter", "overlay-parameter", "SCRIPTORIUM_EXPLAIN_KEY"}},
|
||||
} {
|
||||
code, stdout, stderr := runCLICommand(t, configCommand, append([]string{"explain", "--path", tc.pointer}, args...))
|
||||
if code != ExitOK || stderr != "" || strings.Contains(stdout, keyValue) {
|
||||
t.Fatalf("explain %q failed or disclosed key: code=%d out=%q err=%q", tc.pointer, code, stdout, stderr)
|
||||
}
|
||||
for _, want := range tc.want {
|
||||
if !strings.Contains(stdout, want) {
|
||||
t.Fatalf("explain %q lacks %q: %q", tc.pointer, want, stdout)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := os.Unsetenv("SCRIPTORIUM_EXPLAIN_KEY"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
code, stdout, stderr := runCLICommand(t, configCommand, append([]string{"explain", "--path", "/backends/team~1blue~01/api_key_env"}, args...))
|
||||
if code != ExitOK || stderr != "" || !strings.Contains(stdout, "SCRIPTORIUM_EXPLAIN_KEY") || strings.Contains(stdout, keyValue) {
|
||||
t.Fatalf("absent credential changed reference explanation: code=%d out=%q err=%q", code, stdout, stderr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigExplainRejectsUnknownOrMalformedPaths(t *testing.T) {
|
||||
path := writeAppConfigFile(t, "{}\n")
|
||||
for _, args := range [][]string{
|
||||
{"explain", "--config", path},
|
||||
{"explain", "--config", path, "--path", "not-a-pointer"},
|
||||
{"explain", "--config", path, "--path", "/missing"},
|
||||
} {
|
||||
code, stdout, stderr := runCLICommand(t, configCommand, args)
|
||||
if code != ExitRuntimeError || stdout != "" || stderr == "" {
|
||||
t.Fatalf("invalid explanation path accepted: args=%q code=%d out=%q err=%q", args, code, stdout, stderr)
|
||||
}
|
||||
}
|
||||
var stderr bytes.Buffer
|
||||
if code := configCommand([]string{"explain", "--config", path, "--path", ""}, errorOutputWriter{}, &stderr); code != ExitRuntimeError || !strings.Contains(stderr.String(), "output error") {
|
||||
t.Fatalf("explanation writer failure not reported: code=%d err=%q", code, stderr.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -760,5 +760,5 @@ func printUsage(w io.Writer) {
|
||||
fmt.Fprintf(w, " serve: scriptorium serve [--config PATH] [--config-overlay NAME] [--addr %s] [--prompt-dir DIR] [--profile-dir DIR] [--schema-dir DIR] [--artifact-root DIR] [--max-request-bytes N] [--max-artifact-bytes N] [--max-response-bytes N]\n", defaults.HTTPAddrDefault)
|
||||
fmt.Fprintln(w, " inspect prompt: scriptorium inspect prompt --prompt ID [--prompt-version VERSION] [--config PATH] [--config-overlay NAME] [--prompt-dir DIR] [--format text|json] [--out path]")
|
||||
fmt.Fprintln(w, " inspect profile: scriptorium inspect profile --profile ID [--config PATH] [--config-overlay NAME] [--profile-dir DIR] [--format text|json] [--out path]")
|
||||
fmt.Fprintln(w, " config: scriptorium config <validate|show|sources> [--config PATH] [--config-overlay NAME] [application overrides]")
|
||||
fmt.Fprintln(w, " config: scriptorium config <validate|show|sources|explain> [--config PATH] [--config-overlay NAME] [application overrides] [--path /pointer for explain]")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user