package main // The module's read tools (novox/hq issue 1562): what the module wrote to its own `steps` and `toolchains` states, // answered as stored. A long step ends on its key in the steps state (ADR 0469 ยง2) and its result, such as the // workspace a fetch made, is only there; a module reading its own bucket needs no grant. import ( "encoding/json" "fmt" "sort" "strconv" "strings" stdio "git.novox.be/novox/mesh-sdk/go" "git.novox.be/novox/mesh-sdk/go/delivery" ) // The read tools, named as the module is: build_node_ and the verb. const ( StepReadTool = "build_node_step" StepsReadTool = "build_node_steps" ToolchainsReadTool = "build_node_toolchains" ) // StateRead reads one of the module's own states: its keys and a key's stored value (nil for a key with none). type StateRead struct { Keys func() ([]string, error) Get func(key string) (json.RawMessage, error) } // readOf is the read of a state the process keeps. func readOf(s stdio.KeptState) StateRead { return StateRead{Keys: s.Keys, Get: func(key string) (json.RawMessage, error) { e, err := s.Get(key) if err != nil || e == nil { return nil, err } return e.Value, nil }} } // stepKey is the key of a step of this node: the id alone, or the whole key . as a step tool answered it. func (m *Module) stepKey(id string) (string, error) { id = strings.TrimSpace(id) if node, rest, ok := strings.Cut(id, "."); ok { if node != m.Node { return "", fmt.Errorf("the step %q is of node %q, this module runs on %q", id, node, m.Node) } id = rest } return delivery.StepKey(m.Node, id) } // ReadStep is the stored value of this node's step id (state, reason, result). func (m *Module) ReadStep(id string) (delivery.Step, error) { key, err := m.stepKey(id) if err != nil { return delivery.Step{}, err } if m.ReadSteps.Get == nil { return delivery.Step{}, fmt.Errorf("the steps state cannot be read here") } raw, err := m.ReadSteps.Get(key) if err != nil { return delivery.Step{}, err } if len(raw) == 0 { return delivery.Step{}, fmt.Errorf("no step %s in the steps state", key) } return delivery.DecodeStep(raw) } // StepLine is a step without its result: the list answers these. type StepLine struct { ID string `json:"id"` Tool string `json:"tool"` State string `json:"state"` Started string `json:"started"` Ended string `json:"ended,omitempty"` Reason string `json:"reason,omitempty"` } // RecentSteps are this node's steps, the latest started first, at most limit (ten when limit is not above zero). func (m *Module) RecentSteps(limit int) ([]StepLine, error) { if limit <= 0 { limit = 10 } if m.ReadSteps.Keys == nil || m.ReadSteps.Get == nil { return nil, fmt.Errorf("the steps state cannot be read here") } keys, err := m.ReadSteps.Keys() if err != nil { return nil, err } var steps []delivery.Step for _, k := range keys { if node, _, err := delivery.ParseStepKey(k); err != nil || node != m.Node { continue } raw, err := m.ReadSteps.Get(k) if err != nil { return nil, err } if len(raw) == 0 { continue } s, err := delivery.DecodeStep(raw) if err != nil { continue // a value this module cannot read is not one of its own steps } steps = append(steps, s) } sort.SliceStable(steps, func(i, j int) bool { return steps[i].Started.After(steps[j].Started) }) if len(steps) > limit { steps = steps[:limit] } out := make([]StepLine, 0, len(steps)) for _, s := range steps { l := StepLine{ID: s.ID, Tool: s.Tool, State: s.State, Started: s.Started.UTC().Format("2006-01-02T15:04:05Z"), Reason: s.Reason} if !s.Ended.IsZero() { l.Ended = s.Ended.UTC().Format("2006-01-02T15:04:05Z") } out = append(out, l) } return out, nil } // ReadToolchains is every key of the toolchains state (.) with its stored value. func (m *Module) ReadToolchains() (map[string]delivery.Toolchain, error) { if m.ReadToolchainsState.Keys == nil || m.ReadToolchainsState.Get == nil { return nil, fmt.Errorf("the toolchains state cannot be read here") } keys, err := m.ReadToolchainsState.Keys() if err != nil { return nil, err } out := map[string]delivery.Toolchain{} for _, k := range keys { raw, err := m.ReadToolchainsState.Get(k) if err != nil { return nil, err } if len(raw) == 0 { continue } t, err := delivery.DecodeToolchain(raw) if err != nil { return nil, fmt.Errorf("%s: %w", k, err) } out[k] = t } return out, nil } // ReadTools are the module's read tools. All are read-only. func (m *Module) ReadTools() []stdio.Tool { return []stdio.Tool{ {Name: StepReadTool, Description: "What one step of this machine ended with, as stored in the module's steps state: its tool, state " + "(running, passed, failed, refused), when it started and ended, why it failed or was refused, and its result " + "(a fetch's workspace id is there). id is the step id a step tool answered, or the whole key .. Read-only.", Input: map[string]any{"type": "object", "required": []string{"id"}, "properties": map[string]any{ "id": map[string]any{"type": "string", "description": "the step id, or the key ."}}}, Run: func(args map[string]any) (any, error) { id, _ := args["id"].(string) return m.ReadStep(id) }}, {Name: StepsReadTool, Description: "This machine's latest steps from the module's steps state, newest first, each with its id, tool, state, " + "times and reason but not its result (read one with " + StepReadTool + "). Read-only.", Input: map[string]any{"type": "object", "properties": map[string]any{ "limit": map[string]any{"type": "integer", "description": "how many, at most; 10 when left out"}}}, Run: func(args map[string]any) (any, error) { steps, err := m.RecentSteps(intArg(args["limit"])) return map[string]any{"node": m.Node, "steps": steps}, err }}, {Name: ToolchainsReadTool, Description: "What the module's toolchains state holds: each key . with the version installed, the " + "recipe version and when the module saw them. Read-only.", Run: func(map[string]any) (any, error) { keys, err := m.ReadToolchains() return map[string]any{"node": m.Node, "toolchains": keys}, err }}, } } // intArg is a tool argument that is a number, or a string of one; zero otherwise. func intArg(v any) int { switch n := v.(type) { case float64: return int(n) case int: return n case string: i, _ := strconv.Atoi(n) return i } return 0 }