/
waiter.go
121 lines (102 loc) · 3.06 KB
/
waiter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
// Copyright 2024 HUMAN Security.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package docker
import (
"context"
"fmt"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/stdcopy"
"regexp"
"strings"
"time"
)
// WaitForLog creates a waiter for waiting until a specific string is found in the container logs.
func WaitForLog(s string) Waiter {
return Waiter{
Type: WaiterTypeString,
String: s,
}
}
// WaitForLogRegex creates a waiter for waiting until a specific regular expression is matched in the container logs.
func WaitForLogRegex(regexp string) Waiter {
return Waiter{
Type: WaiterTypeRegex,
Regex: regexp,
}
}
// WaitForDuration creates a waiter for waiting for a specific duration.
func WaitForDuration(duration string) Waiter {
return Waiter{
Type: WaiterTypeDuration,
Duration: duration,
}
}
// waiterFunc is a function signature for the different types of waiters.
type waiterFunc func(ctx context.Context, cli *client.Client, containerID string, isNewContainer bool) error
// validateWaiter validates the provided waiter and returns the corresponding waiterFunc.
func validateWaiter(w Waiter) (waiterFunc, error) {
switch w.Type {
case WaiterTypeString:
return func(ctx context.Context, cli *client.Client, containerID string, _ bool) error {
var reached bool
err := followLogs(ctx, cli, containerID, func(_ time.Time, text string, _ stdcopy.StdType) (stop bool) {
reached = strings.Contains(text, w.String)
return reached
})
if err != nil {
return err
}
if reached {
return nil
}
return ErrContainerStopped{without: fmt.Sprintf("reaching log '%s'", w.String)}
}, nil
case WaiterTypeRegex:
re, err := regexp.Compile(w.Regex)
if err != nil {
return nil, err
}
return func(ctx context.Context, cli *client.Client, containerID string, _ bool) error {
var reached bool
err := followLogs(ctx, cli, containerID, func(_ time.Time, text string, _ stdcopy.StdType) (stop bool) {
reached = re.MatchString(text)
return reached
})
if err != nil {
return err
}
if reached {
return nil
}
return ErrContainerStopped{without: fmt.Sprintf("reaching log regex '%s'", w.String)}
}, nil
case WaiterTypeDuration:
d, err := time.ParseDuration(w.Duration)
if err != nil {
return nil, err
}
return func(_ context.Context, _ *client.Client, _ string, isNewContainer bool) error {
if !isNewContainer {
return nil
}
time.Sleep(d)
return nil
}, nil
}
return nil, ErrInvalidWaiterType{Type: w.Type}
}
// ErrInvalidWaiterType represents an error for an invalid waiter type.
type ErrInvalidWaiterType struct {
Type WaiterType
}
func (e ErrInvalidWaiterType) Error() string {
return fmt.Sprintf("invalid waiter type %s", e.Type)
}
// ErrContainerStopped represents an error when the container stops without reaching the expected condition.
type ErrContainerStopped struct {
without string
}
func (e ErrContainerStopped) Error() string {
return fmt.Sprintf("container stopped without %s", e.without)
}