1package wait
2
3import (
4	"context"
5	"io/ioutil"
6	"strings"
7	"time"
8)
9
10// Implement interface
11var _ Strategy = (*LogStrategy)(nil)
12
13// LogStrategy will wait until a given log entry shows up in the docker logs
14type LogStrategy struct {
15	// all Strategies should have a startupTimeout to avoid waiting infinitely
16	startupTimeout time.Duration
17
18	// additional properties
19	Log          string
20	PollInterval time.Duration
21	Occurrence   int
22}
23
24// NewLogStrategy constructs a HTTP strategy waiting on port 80 and status code 200
25func NewLogStrategy(log string) *LogStrategy {
26	return &LogStrategy{
27		startupTimeout: defaultStartupTimeout(),
28		Log:            log,
29		PollInterval:   100 * time.Millisecond,
30		Occurrence:     1,
31	}
32
33}
34
35// fluent builders for each property
36// since go has neither covariance nor generics, the return type must be the type of the concrete implementation
37// this is true for all properties, even the "shared" ones like startupTimeout
38
39// WithStartupTimeout can be used to change the default startup timeout
40func (ws *LogStrategy) WithStartupTimeout(startupTimeout time.Duration) *LogStrategy {
41	ws.startupTimeout = startupTimeout
42	return ws
43}
44
45// WithPollInterval can be used to override the default polling interval of 100 milliseconds
46func (ws *LogStrategy) WithPollInterval(pollInterval time.Duration) *LogStrategy {
47	ws.PollInterval = pollInterval
48	return ws
49}
50
51func (ws *LogStrategy) WithOccurrence(o int) *LogStrategy {
52	// the number of occurence needs to be positive
53	if o <= 0 {
54		o = 1
55	}
56	ws.Occurrence = o
57	return ws
58}
59
60// ForLog is the default construction for the fluid interface.
61//
62// For Example:
63// wait.
64//     ForLog("some text").
65//     WithPollInterval(1 * time.Second)
66func ForLog(log string) *LogStrategy {
67	return NewLogStrategy(log)
68}
69
70// WaitUntilReady implements Strategy.WaitUntilReady
71func (ws *LogStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) (err error) {
72	// limit context to startupTimeout
73	ctx, cancelContext := context.WithTimeout(ctx, ws.startupTimeout)
74	defer cancelContext()
75	currentOccurence := 0
76
77LOOP:
78	for {
79		select {
80		case <-ctx.Done():
81			return ctx.Err()
82		default:
83			reader, err := target.Logs(ctx)
84
85			if err != nil {
86				time.Sleep(ws.PollInterval)
87				continue
88			}
89			b, err := ioutil.ReadAll(reader)
90			logs := string(b)
91			if strings.Contains(logs, ws.Log) {
92				currentOccurence++
93				if ws.Occurrence == 0 || currentOccurence >= ws.Occurrence-1 {
94					break LOOP
95				}
96			} else {
97				time.Sleep(ws.PollInterval)
98				continue
99			}
100		}
101	}
102
103	return nil
104}
105