Fix dependencies

This commit is contained in:
2020-03-01 17:06:34 +01:00
parent fee4454c12
commit 6c9b3f3e5c
921 changed files with 329221 additions and 13 deletions

View File

@ -0,0 +1,116 @@
package wait
import (
"context"
"fmt"
"net"
"os"
"strconv"
"syscall"
"time"
"github.com/pkg/errors"
"github.com/docker/go-connections/nat"
)
// Implement interface
var _ Strategy = (*HostPortStrategy)(nil)
type HostPortStrategy struct {
Port nat.Port
// all WaitStrategies should have a startupTimeout to avoid waiting infinitely
startupTimeout time.Duration
}
// NewHostPortStrategy constructs a default host port strategy
func NewHostPortStrategy(port nat.Port) *HostPortStrategy {
return &HostPortStrategy{
Port: port,
startupTimeout: defaultStartupTimeout(),
}
}
// fluent builders for each property
// since go has neither covariance nor generics, the return type must be the type of the concrete implementation
// this is true for all properties, even the "shared" ones like startupTimeout
// ForListeningPort is a helper similar to those in Wait.java
// https://github.com/testcontainers/testcontainers-java/blob/1d85a3834bd937f80aad3a4cec249c027f31aeb4/core/src/main/java/org/testcontainers/containers/wait/strategy/Wait.java
func ForListeningPort(port nat.Port) *HostPortStrategy {
return NewHostPortStrategy(port)
}
func (hp *HostPortStrategy) WithStartupTimeout(startupTimeout time.Duration) *HostPortStrategy {
hp.startupTimeout = startupTimeout
return hp
}
// WaitUntilReady implements Strategy.WaitUntilReady
func (hp *HostPortStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) (err error) {
// limit context to startupTimeout
ctx, cancelContext := context.WithTimeout(ctx, hp.startupTimeout)
defer cancelContext()
ipAddress, err := target.Host(ctx)
if err != nil {
return
}
port, err := target.MappedPort(ctx, hp.Port)
if err != nil {
return
}
proto := port.Proto()
portNumber := port.Int()
portString := strconv.Itoa(portNumber)
//external check
dialer := net.Dialer{}
address := net.JoinHostPort(ipAddress, portString)
for {
conn, err := dialer.DialContext(ctx, proto, address)
if err != nil {
if v, ok := err.(*net.OpError); ok {
if v2, ok := (v.Err).(*os.SyscallError); ok {
if v2.Err == syscall.ECONNREFUSED && ctx.Err() == nil {
time.Sleep(100 * time.Millisecond)
continue
}
}
}
return err
} else {
conn.Close()
break
}
}
//internal check
command := buildInternalCheckCommand(hp.Port.Int())
for {
exitCode, err := target.Exec(ctx, []string{"/bin/sh", "-c", command})
if err != nil {
return errors.Wrapf(err, "host port waiting failed")
}
if exitCode == 0 {
break
} else if exitCode == 126 {
return errors.New("/bin/sh command not executable")
}
}
return nil
}
func buildInternalCheckCommand(internalPort int) string {
command := `(
cat /proc/net/tcp* | awk '{print $2}' | grep -i :%04x ||
nc -vz -w 1 localhost %d ||
/bin/sh -c '</dev/tcp/localhost/%d'
)
`
return "true && " + fmt.Sprintf(command, internalPort, internalPort, internalPort)
}

View File

@ -0,0 +1,147 @@
package wait
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
"strconv"
"time"
"github.com/docker/go-connections/nat"
)
// Implement interface
var _ Strategy = (*HTTPStrategy)(nil)
type HTTPStrategy struct {
// all Strategies should have a startupTimeout to avoid waiting infinitely
startupTimeout time.Duration
// additional properties
Port nat.Port
Path string
StatusCodeMatcher func(status int) bool
UseTLS bool
AllowInsecure bool
}
// NewHTTPStrategy constructs a HTTP strategy waiting on port 80 and status code 200
func NewHTTPStrategy(path string) *HTTPStrategy {
return &HTTPStrategy{
startupTimeout: defaultStartupTimeout(),
Port: "80/tcp",
Path: path,
StatusCodeMatcher: defaultStatusCodeMatcher,
UseTLS: false,
}
}
func defaultStatusCodeMatcher(status int) bool {
return status == http.StatusOK
}
// fluent builders for each property
// since go has neither covariance nor generics, the return type must be the type of the concrete implementation
// this is true for all properties, even the "shared" ones like startupTimeout
func (ws *HTTPStrategy) WithStartupTimeout(startupTimeout time.Duration) *HTTPStrategy {
ws.startupTimeout = startupTimeout
return ws
}
func (ws *HTTPStrategy) WithPort(port nat.Port) *HTTPStrategy {
ws.Port = port
return ws
}
func (ws *HTTPStrategy) WithStatusCodeMatcher(statusCodeMatcher func(status int) bool) *HTTPStrategy {
ws.StatusCodeMatcher = statusCodeMatcher
return ws
}
func (ws *HTTPStrategy) WithTLS(useTLS bool) *HTTPStrategy {
ws.UseTLS = useTLS
return ws
}
func (ws *HTTPStrategy) WithAllowInsecure(allowInsecure bool) *HTTPStrategy {
ws.AllowInsecure = allowInsecure
return ws
}
// ForHTTP is a convenience method similar to Wait.java
// https://github.com/testcontainers/testcontainers-java/blob/1d85a3834bd937f80aad3a4cec249c027f31aeb4/core/src/main/java/org/testcontainers/containers/wait/strategy/Wait.java
func ForHTTP(path string) *HTTPStrategy {
return NewHTTPStrategy(path)
}
// WaitUntilReady implements Strategy.WaitUntilReady
func (ws *HTTPStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) (err error) {
// limit context to startupTimeout
ctx, cancelContext := context.WithTimeout(ctx, ws.startupTimeout)
defer cancelContext()
ipAddress, err := target.Host(ctx)
if err != nil {
return
}
port, err := target.MappedPort(ctx, ws.Port)
if err != nil {
return
}
if port.Proto() != "tcp" {
return errors.New("Cannot use HTTP client on non-TCP ports")
}
portNumber := port.Int()
portString := strconv.Itoa(portNumber)
address := net.JoinHostPort(ipAddress, portString)
var proto string
if ws.UseTLS {
proto = "https"
} else {
proto = "http"
}
url := fmt.Sprintf("%s://%s%s", proto, address, ws.Path)
tripper := http.DefaultTransport
if ws.AllowInsecure {
tripper.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
}
client := http.Client{Timeout: ws.startupTimeout, Transport: tripper}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return err
}
req = req.WithContext(ctx)
Retry:
for {
select {
case <-ctx.Done():
break Retry
default:
resp, err := client.Do(req)
if err != nil || !ws.StatusCodeMatcher(resp.StatusCode) {
time.Sleep(100 * time.Millisecond)
continue
}
break Retry
}
}
return nil
}

View File

@ -0,0 +1,104 @@
package wait
import (
"context"
"io/ioutil"
"strings"
"time"
)
// Implement interface
var _ Strategy = (*LogStrategy)(nil)
// LogStrategy will wait until a given log entry shows up in the docker logs
type LogStrategy struct {
// all Strategies should have a startupTimeout to avoid waiting infinitely
startupTimeout time.Duration
// additional properties
Log string
PollInterval time.Duration
Occurrence int
}
// NewLogStrategy constructs a HTTP strategy waiting on port 80 and status code 200
func NewLogStrategy(log string) *LogStrategy {
return &LogStrategy{
startupTimeout: defaultStartupTimeout(),
Log: log,
PollInterval: 100 * time.Millisecond,
Occurrence: 1,
}
}
// fluent builders for each property
// since go has neither covariance nor generics, the return type must be the type of the concrete implementation
// this is true for all properties, even the "shared" ones like startupTimeout
// WithStartupTimeout can be used to change the default startup timeout
func (ws *LogStrategy) WithStartupTimeout(startupTimeout time.Duration) *LogStrategy {
ws.startupTimeout = startupTimeout
return ws
}
// WithPollInterval can be used to override the default polling interval of 100 milliseconds
func (ws *LogStrategy) WithPollInterval(pollInterval time.Duration) *LogStrategy {
ws.PollInterval = pollInterval
return ws
}
func (ws *LogStrategy) WithOccurrence(o int) *LogStrategy {
// the number of occurence needs to be positive
if o <= 0 {
o = 1
}
ws.Occurrence = o
return ws
}
// ForLog is the default construction for the fluid interface.
//
// For Example:
// wait.
// ForLog("some text").
// WithPollInterval(1 * time.Second)
func ForLog(log string) *LogStrategy {
return NewLogStrategy(log)
}
// WaitUntilReady implements Strategy.WaitUntilReady
func (ws *LogStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) (err error) {
// limit context to startupTimeout
ctx, cancelContext := context.WithTimeout(ctx, ws.startupTimeout)
defer cancelContext()
currentOccurence := 0
LOOP:
for {
select {
case <-ctx.Done():
return ctx.Err()
default:
reader, err := target.Logs(ctx)
if err != nil {
time.Sleep(ws.PollInterval)
continue
}
b, err := ioutil.ReadAll(reader)
logs := string(b)
if strings.Contains(logs, ws.Log) {
currentOccurence++
if ws.Occurrence == 0 || currentOccurence >= ws.Occurrence-1 {
break LOOP
}
} else {
time.Sleep(ws.PollInterval)
continue
}
}
}
return nil
}

View File

@ -0,0 +1,47 @@
package wait
import (
"context"
"fmt"
"time"
)
// Implement interface
var _ Strategy = (*MultiStrategy)(nil)
type MultiStrategy struct {
// all Strategies should have a startupTimeout to avoid waiting infinitely
startupTimeout time.Duration
// additional properties
Strategies []Strategy
}
func (ms *MultiStrategy) WithStartupTimeout(startupTimeout time.Duration) *MultiStrategy {
ms.startupTimeout = startupTimeout
return ms
}
func ForAll(strategies ...Strategy) *MultiStrategy {
return &MultiStrategy{
startupTimeout: defaultStartupTimeout(),
Strategies: strategies,
}
}
func (ms *MultiStrategy) WaitUntilReady(ctx context.Context, target StrategyTarget) (err error) {
ctx, cancelContext := context.WithTimeout(ctx, ms.startupTimeout)
defer cancelContext()
if len(ms.Strategies) == 0 {
return fmt.Errorf("no wait strategy supplied")
}
for _, strategy := range ms.Strategies {
err := strategy.WaitUntilReady(ctx, target)
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,66 @@
package wait
import (
"context"
"database/sql"
"fmt"
"github.com/docker/go-connections/nat"
"time"
)
//ForSQL constructs a new waitForSql strategy for the given driver
func ForSQL(port nat.Port, driver string, url func(nat.Port) string) *waitForSql {
return &waitForSql{
Port: port,
URL: url,
Driver: driver,
}
}
type waitForSql struct {
URL func(port nat.Port) string
Driver string
Port nat.Port
startupTimeout time.Duration
}
//Timeout sets the maximum waiting time for the strategy after which it'll give up and return an error
func (w *waitForSql) Timeout(duration time.Duration) *waitForSql {
w.startupTimeout = duration
return w
}
//WaitUntilReady repeatedly tries to run "SELECT 1" query on the given port using sql and driver.
// If the it doesn't succeed until the timeout value which defaults to 10 seconds, it will return an error
func (w *waitForSql) WaitUntilReady(ctx context.Context, target StrategyTarget) (err error) {
if w.startupTimeout == 0 {
w.startupTimeout = time.Second * 10
}
ctx, cancel := context.WithTimeout(ctx, w.startupTimeout)
defer cancel()
ticker := time.NewTicker(time.Millisecond * 100)
defer ticker.Stop()
port, err := target.MappedPort(ctx, w.Port)
if err != nil {
return fmt.Errorf("target.MappedPort: %v", err)
}
db, err := sql.Open(w.Driver, w.URL(port))
if err != nil {
return fmt.Errorf("sql.Open: %v", err)
}
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
if _, err := db.ExecContext(ctx, "SELECT 1"); err != nil {
continue
}
return nil
}
}
}

View File

@ -0,0 +1,24 @@
package wait
import (
"context"
"io"
"time"
"github.com/docker/go-connections/nat"
)
type Strategy interface {
WaitUntilReady(context.Context, StrategyTarget) error
}
type StrategyTarget interface {
Host(context.Context) (string, error)
MappedPort(context.Context, nat.Port) (nat.Port, error)
Logs(context.Context) (io.ReadCloser, error)
Exec(ctx context.Context, cmd []string) (int, error)
}
func defaultStartupTimeout() time.Duration {
return 60 * time.Second
}