From 66bf26c0d721c0b759575d11d7c40c4a8be4c965 Mon Sep 17 00:00:00 2001 From: jochen Date: Sat, 24 Oct 2026 04:10:31 +0200 Subject: [PATCH] Let the composer read its request from a file, since a run step hands it no standard input (issue 1622) --- go/cmd/mesh-compose/main.go | 15 ++++++++++++--- go/cmd/mesh-compose/main_test.go | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/go/cmd/mesh-compose/main.go b/go/cmd/mesh-compose/main.go index e871b0a..cc0211d 100644 --- a/go/cmd/mesh-compose/main.go +++ b/go/cmd/mesh-compose/main.go @@ -4,13 +4,15 @@ // it answers what that commit's composition answers, so a program that links one mesh-sdk can have another's judged. // // mesh-compose < request.json > verdict.json +// mesh-compose -request request.json > verdict.json // mesh-compose -catalogue [-node ]... [-depth resolve|compose] [-declarations] [< request.json] // // -catalogue reads /modules/*/module.json and a module.json at 's root as the first case's manifests (a // checkout or `git archive` of the catalogue repository at a ref: the command fetches nothing). -node adds a machine to // compose; -declarations answers each machine's declared paths with their owners and modes. // -// It opens no network connection, bus, store or file beyond its standard input and the -catalogue directory, and reads +// It opens no network connection, bus, store or file beyond its standard input, the -request file and the -catalogue +// directory, and reads // no environment. Exit codes: 0 the verdict is written (a machine that does not compose is a verdict, not an error); // 1 the request cannot be read or run, said on standard error; 2 the flags are wrong. package main @@ -47,6 +49,7 @@ func run(args []string, stdin io.Reader, stdout, stderr io.Writer) int { fs.Var(&machines, "node", "a machine to compose (repeatable)") depth := fs.String("depth", "", "resolve or compose (default: the request's, else compose)") declarations := fs.Bool("declarations", false, "answer each composed machine's declared paths, owners and modes") + requestFile := fs.String("request", "", "read the request from this file instead of standard input (a run step that hands no standard input)") if err := fs.Parse(args); err != nil { return 2 } @@ -54,9 +57,15 @@ func run(args []string, stdin io.Reader, stdout, stderr io.Writer) int { fmt.Fprintf(stderr, "mesh-compose: unexpected arguments %q\n", fs.Args()) return 2 } - raw, err := io.ReadAll(stdin) + var raw []byte + var err error + if *requestFile != "" { + raw, err = os.ReadFile(*requestFile) + } else { + raw, err = io.ReadAll(stdin) + } if err != nil { - fmt.Fprintln(stderr, "mesh-compose: standard input cannot be read:", err) + fmt.Fprintln(stderr, "mesh-compose: the request cannot be read:", err) return 1 } var req composer.Request diff --git a/go/cmd/mesh-compose/main_test.go b/go/cmd/mesh-compose/main_test.go index 358e743..a878b74 100644 --- a/go/cmd/mesh-compose/main_test.go +++ b/go/cmd/mesh-compose/main_test.go @@ -184,6 +184,24 @@ func TestACatalogueCheckoutIsComposedForANode(t *testing.T) { } } +// A run step hands the command no standard input: the request is read from the file -request names, and a file that is +// not there is a request it cannot read. +func TestTheRequestIsReadFromAFile(t *testing.T) { + file := filepath.Join(t.TempDir(), "request.json") + r := composer.Request{V: composer.Version, Cases: []composer.Case{{Name: "only", Manifests: map[string]composer.Manifest{ + "demo": {Raw: json.RawMessage(`{"module":"demo","version":"1"}`)}}}}} + if err := os.WriteFile(file, encode(t, r), 0o600); err != nil { + t.Fatal(err) + } + code, v, stderr := runJSON(t, []string{"-request", file}, nil) + if code != 0 || len(v.Cases) != 1 || !v.Cases[0].Manifests["demo"].Accepted { + t.Fatalf("exit %d (%s), verdict %+v", code, stderr, v) + } + if code, _, stderr := runJSON(t, []string{"-request", file + ".gone"}, nil); code != 1 || !strings.Contains(stderr, "cannot be read") { + t.Errorf("a request file that is not there: exit %d, %q", code, stderr) + } +} + // The exit codes: 1 for a request that cannot be read or run, 2 for wrong flags, each said on standard error. func TestTheExitCodesSayWhatFailed(t *testing.T) { unfilled := encode(t, composer.Request{V: composer.Version, Cases: []composer.Case{{Name: "c", Manifests: map[string]composer.Manifest{"x": {Digest: "abc"}}}}})