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