package build import ( "context" "encoding/json" "fmt" "time" "git.novox.be/novox/mesh-sdk/go/delivery" ) // Caller calls a tool of a module on one named machine (mesh-sdk process.Conn.CallOn; ADR 0469 clause 3). type Caller interface { CallOn(node, key string, body any) (json.RawMessage, error) } // Steps waits for the end of a build module's long step: the value of its key `.` in the module's steps state // (ADR 0469 clause 2) once its state is not running. It is a watch of that key, not a poll; it returns when the step // ends or ctx does. type Steps interface { Wait(ctx context.Context, module, key string) (delivery.Step, error) } // Runner runs a build module's long steps: the call answers at once with the step's key, and the result is read from that // key when the step ends (ADR 0469 clause 2). type Runner struct { Caller Caller Steps Steps // Bound is how long a step may run before the builder stops waiting for it. Bound time.Duration } // Run calls tool of the build module on node with args, waits for the step's end and answers its result. A step that // failed or was refused is an error naming the node, the state and the reason. func (r Runner) Run(ctx context.Context, module, node, tool string, args map[string]any) (json.RawMessage, error) { raw, err := r.Caller.CallOn(node, module+"."+tool, args) if err != nil { return nil, fmt.Errorf("%s on %s: %w", tool, node, err) } var answer struct { Step string `json:"step"` Key string `json:"key"` State string `json:"state"` } if err := json.Unmarshal(raw, &answer); err != nil { return nil, fmt.Errorf("%s on %s answered what is not a step: %w", tool, node, err) } if answer.Key == "" { return nil, fmt.Errorf("%s on %s answered no step key: %s", tool, node, raw) } if n, _, err := delivery.ParseStepKey(answer.Key); err != nil || n != node { return nil, fmt.Errorf("%s on %s answered the step key %q, which is not a key of that node", tool, node, answer.Key) } wait, cancel := context.WithTimeoutCause(ctx, r.Bound, fmt.Errorf("the step %s did not end within %s", answer.Key, r.Bound)) defer cancel() step, err := r.Steps.Wait(wait, module, answer.Key) if err != nil { if cause := context.Cause(wait); cause != nil && ctx.Err() == nil { err = cause } return nil, fmt.Errorf("%s on %s: %w", tool, node, err) } if step.State != delivery.StepPassed { return nil, fmt.Errorf("%s on %s: the step %s %s: %s", tool, node, answer.Key, step.State, step.Reason) } return step.Result, nil }