1package exec
2
3import (
4	"context"
5	"errors"
6	"time"
7)
8
9// TimeoutStep applies a fixed timeout to a step's Run.
10type TimeoutStep struct {
11	step     Step
12	duration string
13	timedOut bool
14}
15
16// Timeout constructs a TimeoutStep factory.
17func Timeout(step Step, duration string) *TimeoutStep {
18	return &TimeoutStep{
19		step:     step,
20		duration: duration,
21		timedOut: false,
22	}
23}
24
25// Run parses the timeout duration and invokes the nested step.
26//
27// If the nested step takes longer than the duration, it is sent the Interrupt
28// signal, and the TimeoutStep returns nil once the nested step exits (ignoring
29// the nested step's error).
30//
31// The result of the nested step's Run is returned.
32func (ts *TimeoutStep) Run(ctx context.Context, state RunState) error {
33	parsedDuration, err := time.ParseDuration(ts.duration)
34	if err != nil {
35		return err
36	}
37
38	timeoutCtx, cancel := context.WithTimeout(ctx, parsedDuration)
39	defer cancel()
40
41	err = ts.step.Run(timeoutCtx, state)
42	if errors.Is(err, context.DeadlineExceeded) {
43		ts.timedOut = true
44		return nil
45	}
46
47	return err
48}
49
50// Succeeded is true if the nested step completed successfully
51// and did not time out.
52func (ts *TimeoutStep) Succeeded() bool {
53	return !ts.timedOut && ts.step.Succeeded()
54}
55