68 lines
2.5 KiB
Go
68 lines
2.5 KiB
Go
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 `<node>.<id>` 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
|
|
}
|