Give the build verb the command's other two shapes, so a changed base or a source that moved can be rebuilt through the console
The verb took only a repository; build --on <module> and build --behind were reachable only through the generic command verb. Each shape is one per call, and a second beside it is refused as passed over.
This commit is contained in:
@@ -694,8 +694,19 @@ func (a *verbArguments) commandLine() ([]string, error) {
|
|||||||
}
|
}
|
||||||
return []string{"module", "issue", str("module"), "--node", str("node")}, nil
|
return []string{"module", "issue", str("module"), "--node", str("node")}, nil
|
||||||
case "build":
|
case "build":
|
||||||
if err := need("repository"); err != nil {
|
// The command's three shapes (`build --on`, `build --behind`, `build <repository>`), one per
|
||||||
return nil, err
|
// call: each branch reads only its own argument, so another given beside it is refused as
|
||||||
|
// passed over rather than dropped. Asked and not waited for, the same as a single build.
|
||||||
|
if b := str("on"); b != "" {
|
||||||
|
return []string{"build", "--on", b, "--wait", "0"}, nil
|
||||||
|
}
|
||||||
|
if on("behind") {
|
||||||
|
return []string{"build", "--behind", "--wait", "0"}, nil
|
||||||
|
}
|
||||||
|
if a.given["repository"] == "" {
|
||||||
|
// No shape named: the command says its usage, which names all three — the answer the
|
||||||
|
// caller needs, and the same as `rotate` given neither of its shapes.
|
||||||
|
return []string{"build"}, nil
|
||||||
}
|
}
|
||||||
// Not waited for: a tool call cannot hold a connection for the minutes a build takes; the
|
// Not waited for: a tool call cannot hold a connection for the minutes a build takes; the
|
||||||
// daemon takes the outcome in when it comes and the id follows the build (issue 176). A
|
// daemon takes the outcome in when it comes and the id follows the build (issue 176). A
|
||||||
|
|||||||
@@ -263,8 +263,6 @@ var accountedFlags = map[string]map[string]string{
|
|||||||
"wait": "set by the verb to 0: the id follows the build (issue 176)",
|
"wait": "set by the verb to 0: the id follows the build (issue 176)",
|
||||||
"self": "set by the verb from the repository's form: a path on the forge, or a URL",
|
"self": "set by the verb from the repository's form: a path on the forge, or a URL",
|
||||||
"dry-run": "withheld: a dry run answers only when the build ends, which a call cannot wait for; `command` reaches it",
|
"dry-run": "withheld: a dry run answers only when the build ends, which a call cannot wait for; `command` reaches it",
|
||||||
"behind": "withheld: the named verb builds one named repository; `command` reaches the rest",
|
|
||||||
"on": "withheld: the named verb builds one named repository; `command` reaches the rest",
|
|
||||||
},
|
},
|
||||||
"builds": {"n": "=limit"},
|
"builds": {"n": "=limit"},
|
||||||
"plans": {"n": "=limit", "what-if": "=repository"},
|
"plans": {"n": "=limit", "what-if": "=repository"},
|
||||||
|
|||||||
@@ -36,6 +36,52 @@ func TestTheBuildToolTellsAForgePathFromAURL(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The build tool has the command's three shapes: a repository, a base whose dependents are rebuilt
|
||||||
|
// (`--on`), or everything behind its source (`--behind`) — each asked, not waited for, one per call.
|
||||||
|
// Given neither, the command line is what it was: nothing new reaches it.
|
||||||
|
func TestTheBuildToolTakesOnAndBehind(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
args map[string]any
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{map[string]any{"on": "mesh-tools"}, "build --on mesh-tools --wait 0"},
|
||||||
|
{map[string]any{"behind": "true"}, "build --behind --wait 0"},
|
||||||
|
{map[string]any{"behind": true}, "build --behind --wait 0"},
|
||||||
|
{map[string]any{"repository": "novox/x"}, "build novox/x --wait 0 --self"},
|
||||||
|
{map[string]any{"repository": "novox/x", "behind": "false"}, "build novox/x --wait 0 --self"},
|
||||||
|
{map[string]any{"repository": "novox/x", "path": "modules/x", "ref": "main"},
|
||||||
|
"build novox/x --wait 0 --self --path modules/x --ref main"},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
argv, err := argvFor("build", c.args)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%v: %v", c.args, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got := strings.Join(argv, " "); got != c.want {
|
||||||
|
t.Errorf("%v became %q, not %q", c.args, got, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Naming no shape runs the bare command, which answers its usage naming all three.
|
||||||
|
if argv, err := argvFor("build", map[string]any{}); err != nil || strings.Join(argv, " ") != "build" {
|
||||||
|
t.Errorf("a build naming nothing became %v, %v", argv, err)
|
||||||
|
}
|
||||||
|
// One shape per call: a second beside it is refused, not dropped, and so is half of one.
|
||||||
|
for _, refused := range []map[string]any{
|
||||||
|
{"path": "modules/x"},
|
||||||
|
{"on": "mesh-tools", "repository": "novox/x"},
|
||||||
|
{"on": "mesh-tools", "behind": "true"},
|
||||||
|
{"behind": "true", "repository": "novox/x"},
|
||||||
|
{"behind": "true", "path": "modules/x"},
|
||||||
|
{"on": "mesh-tools", "ref": "main"},
|
||||||
|
{"behind": "yes"},
|
||||||
|
} {
|
||||||
|
if argv, err := argvFor("build", refused); err == nil {
|
||||||
|
t.Errorf("%v was accepted as %v", refused, argv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// `rotate` is one verb with two shapes (ADR 0114, issue 180): a pair credential by provision, or a
|
// `rotate` is one verb with two shapes (ADR 0114, issue 180): a pair credential by provision, or a
|
||||||
// module's own secret by machine, module and name.
|
// module's own secret by machine, module and name.
|
||||||
func TestRotateTakesAProvisionOrAnOwnSecret(t *testing.T) {
|
func TestRotateTakesAProvisionOrAnOwnSecret(t *testing.T) {
|
||||||
|
|||||||
@@ -396,13 +396,22 @@ var ControllerVerbs = []Verb{
|
|||||||
"machine": "one machine (optional)",
|
"machine": "one machine (optional)",
|
||||||
"retired": "\"true\": only what is retired",
|
"retired": "\"true\": only what is retired",
|
||||||
}, nil, "retired")},
|
}, nil, "retired")},
|
||||||
{Name: "build", Description: "Have the build machine build a repository. Answers at once with the build's id: " +
|
// Three shapes, as the command has them: one repository, every module standing on a base (on), or
|
||||||
"`builds` with that id follows it line by line, and the module is registered when the outcome comes.",
|
// every module behind its source (behind). One per call; a second is refused as passed over.
|
||||||
|
{Name: "build", Description: "Have the build machine build a repository — or, instead, every module that " +
|
||||||
|
"stands on a base (on), or every module behind its source (behind). Answers at once with each build's id: " +
|
||||||
|
"`builds` with an id follows it line by line, and a module is registered when its outcome comes.",
|
||||||
Input: schema(map[string]string{
|
Input: schema(map[string]string{
|
||||||
"repository": "the repository's URL, or its path on the forge holding the git seat (owner/name)",
|
"repository": "the repository's URL, or its path on the forge holding the git seat (owner/name); " +
|
||||||
"path": "the module's directory inside it (optional)",
|
"needed unless on or behind is given",
|
||||||
"ref": "the branch, tag or commit to build (optional)",
|
"path": "with repository: the module's directory inside it (optional)",
|
||||||
}, []string{"repository"})},
|
"ref": "with repository: the branch, tag or commit to build (optional)",
|
||||||
|
"on": "instead of a repository: a module's name. Every module the mesh holds that was built against " +
|
||||||
|
"its artifacts is built again from its own source, bases among them asked first — the rebuild a " +
|
||||||
|
"changed base needs, which behind does not see because their sources did not move",
|
||||||
|
"behind": "\"true\", instead of a repository: build every module the mesh holds whose source has moved " +
|
||||||
|
"past the commit it was built from, bases asked first",
|
||||||
|
}, nil, "behind")},
|
||||||
}
|
}
|
||||||
|
|
||||||
// schema is a JSON schema for an object of string properties, which is every argument the verbs
|
// schema is a JSON schema for an object of string properties, which is every argument the verbs
|
||||||
|
|||||||
@@ -227,3 +227,48 @@ func TestASeatsVerbGainsTheArgumentsTheBinaryNames(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// **A stored row's build verb takes on and behind** (novox/hq ADR 0246: arguments added to a verb are
|
||||||
|
// additive): a row seeded before them described build with a repository it required, so the console
|
||||||
|
// would refuse both and a call naming neither would be refused for want of a repository. Re-seeding
|
||||||
|
// gives the row this binary's build.
|
||||||
|
func TestAStoredRowsBuildVerbTakesOnAndBehind(t *testing.T) {
|
||||||
|
inv := ForTest(t)
|
||||||
|
ctx := t.Context()
|
||||||
|
if _, err := inv.SeedSeats(ctx, catalogue.DefaultSeats()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
old := `[{"name":"build","description":"Have the build machine build a repository.","input":{"type":"object",
|
||||||
|
"properties":{"repository":{"type":"string"},"path":{"type":"string"},"ref":{"type":"string"}},
|
||||||
|
"required":["repository"]}}]`
|
||||||
|
if _, err := inv.store.Pool().Exec(ctx, `update seat set serves = $1 where name = $2`,
|
||||||
|
[]byte(old), catalogue.ControllerSeatName); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := inv.SeedSeats(ctx, catalogue.DefaultSeats()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
seats, err := inv.Seats(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var build catalogue.Verb
|
||||||
|
for _, s := range seats {
|
||||||
|
if s.Name == catalogue.ControllerSeatName {
|
||||||
|
for _, v := range s.Serves {
|
||||||
|
if v.Name == "build" {
|
||||||
|
build = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
props, _ := build.Input["properties"].(map[string]any)
|
||||||
|
for _, arg := range []string{"repository", "path", "ref", "on", "behind"} {
|
||||||
|
if _, has := props[arg]; !has {
|
||||||
|
t.Errorf("build in the row does not take %s: %v", arg, props)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if req, _ := build.Input["required"].([]any); len(req) != 0 {
|
||||||
|
t.Errorf("build in the row still requires %v, so on and behind alone are refused", req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user