diff --git a/cmd/mesh-controller/main.go b/cmd/mesh-controller/main.go index 70346c6..41defbb 100644 --- a/cmd/mesh-controller/main.go +++ b/cmd/mesh-controller/main.go @@ -72,6 +72,8 @@ func run() error { return askCommand(ctx, args[1:]) case "builds": return buildsCommand(ctx, args[1:]) + case "plans": + return plansCommand(ctx, args[1:]) case "pin": return pinCommand(ctx, args[1:], true) case "unpin": diff --git a/cmd/mesh-controller/release_plan.go b/cmd/mesh-controller/release_plan.go index 550a3e9..92521e9 100644 --- a/cmd/mesh-controller/release_plan.go +++ b/cmd/mesh-controller/release_plan.go @@ -2,6 +2,7 @@ package main import ( "context" + "flag" "fmt" "sort" "strings" @@ -524,3 +525,62 @@ func openPlans(plans []inventory.Plan) ([]inventory.Plan, int) { } return open, late } + +// plansCommand says what the last merges produced and where each stands; given an id, one plan +// tier by tier with every module's state. +func plansCommand(ctx context.Context, args []string) error { + set := flag.NewFlagSet("plans", flag.ContinueOnError) + limit := set.Int("n", 10, "how many to show") + positionals, err := parseAround(set, args) + if err != nil { + return err + } + open, err := openStores(ctx) + if err != nil { + return err + } + defer open.Close() + inv := open.inventory + now := time.Now() + if len(positionals) == 1 { + p, err := inv.PlanByID(ctx, positionals[0]) + if err != nil { + return err + } + fmt.Printf("%s — %s\n", p.ID, planLine(p, now)) + for i, tier := range p.Tiers { + marker := " " + if i == p.Tier && p.Open() { + marker = ">" + } + fmt.Printf("%s tier %d\n", marker, i) + for _, m := range tier { + s := p.Modules[m] + state := "not yet asked" + if s != nil && s.State != "" { + state = s.State + if s.Commit != "" { + state += " from " + short(s.Commit) + } + if s.Why != "" { + state += ": " + s.Why + } + } + fmt.Printf(" %-22s %s\n", m, state) + } + } + return nil + } + plans, err := inv.RecentPlans(ctx, *limit) + if err != nil { + return err + } + if len(plans) == 0 { + fmt.Println("no merge has produced a plan yet") + return nil + } + for _, p := range plans { + fmt.Printf("%-28s %s\n", p.ID, planLine(p, now)) + } + return nil +} diff --git a/cmd/mesh-controller/seatverbs.go b/cmd/mesh-controller/seatverbs.go index 5e7f0b6..e95a5dc 100644 --- a/cmd/mesh-controller/seatverbs.go +++ b/cmd/mesh-controller/seatverbs.go @@ -69,6 +69,11 @@ func argvFor(verb string, args map[string]any) ([]string, error) { return []string{"builds", m}, nil } return []string{"builds"}, nil + case "plans": + if id := str("id"); id != "" { + return []string{"plans", id}, nil + } + return []string{"plans"}, nil case "plan": if err := need("node"); err != nil { return nil, err diff --git a/internal/catalogue/verbs.go b/internal/catalogue/verbs.go index 4d573a5..71aeea4 100644 --- a/internal/catalogue/verbs.go +++ b/internal/catalogue/verbs.go @@ -91,6 +91,9 @@ var ControllerVerbs = []Verb{ "module": "one module's name; every module when absent", "log": "a build's id (as `builds` lists it): print what the build machine said, line by line", }, nil)}, + {Name: "plans", Description: "What the last merges produced and where each stands (novox/hq ADR 0162): " + + "the tiers, the tier a plan is at, what it waits for and since when; one plan whole, given its id.", + Input: schema(map[string]string{"id": "a plan's id (as `plans` lists them): that plan, tier by tier"}, nil)}, {Name: "plan", Description: "What one machine would run, and why: the declaration the mesh would send it.", Input: schema(map[string]string{"node": "the machine's name"}, []string{"node"})}, {Name: "assign", Description: "Put a module on a machine. Refused with the mesh's own words when it cannot resolve there.",