Upgrade dependencies

This commit is contained in:
2021-01-17 19:00:46 +01:00
parent ee1ce9e0ae
commit 963220da41
123 changed files with 7786 additions and 3030 deletions

View File

@ -7,3 +7,4 @@ site/
.direnv/
src/mkdocs-codeinclude-plugin
src/pip-delete-this-directory.txt
.idea/

View File

@ -1,6 +1,9 @@
language: go
go:
- stable
- 1.x
- 1.14.x
- 1.15.x
install: true

View File

@ -49,6 +49,7 @@ type Container interface {
NetworkAliases(context.Context) (map[string][]string, error) // get container network aliases for a network
Exec(ctx context.Context, cmd []string) (int, error)
ContainerIP(context.Context) (string, error) // get container ip
CopyFileToContainer(ctx context.Context, hostFilePath string, containerFilePath string, fileMode int64) error
}
// ImageBuildInfo defines what is needed to build an image
@ -80,7 +81,8 @@ type ContainerRequest struct {
Tmpfs map[string]string
RegistryCred string
WaitingFor wait.Strategy
Name string // for specifying container name
Name string // for specifying container name
Hostname string
Privileged bool // for starting privileged container
Networks []string // for specifying network names
NetworkAliases map[string][]string // for specifying network aliases

View File

@ -1,6 +1,7 @@
package testcontainers
import (
"archive/tar"
"bytes"
"context"
"encoding/binary"
@ -11,6 +12,7 @@ import (
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
@ -33,6 +35,11 @@ import (
// Implement interfaces
var _ Container = (*DockerContainer)(nil)
const (
Bridge = "bridge" // Bridge network name (as well as driver)
ReaperDefault = "reaper_default" // Default network name when bridge is not available
)
// DockerContainer represents a container started using Docker
type DockerContainer struct {
// Container ID from Docker
@ -97,7 +104,7 @@ func (c *DockerContainer) PortEndpoint(ctx context.Context, port nat.Port, proto
// Warning: this is based on your Docker host setting. Will fail if using an SSH tunnel
// You can use the "TC_HOST" env variable to set this yourself
func (c *DockerContainer) Host(ctx context.Context) (string, error) {
host, err := c.provider.daemonHost()
host, err := c.provider.daemonHost(ctx)
if err != nil {
return "", err
}
@ -306,6 +313,32 @@ func (c *DockerContainer) Exec(ctx context.Context, cmd []string) (int, error) {
return exitCode, nil
}
func (c *DockerContainer) CopyFileToContainer(ctx context.Context, hostFilePath string, containerFilePath string, fileMode int64) error {
fileContent, err := ioutil.ReadFile(hostFilePath)
if err != nil {
return err
}
buffer := &bytes.Buffer{}
tw := tar.NewWriter(buffer)
defer tw.Close()
hdr := &tar.Header{
Name: filepath.Base(containerFilePath),
Mode: fileMode,
Size: int64(len(fileContent)),
}
if err := tw.WriteHeader(hdr); err != nil {
return err
}
if _, err := tw.Write(fileContent); err != nil {
return err
}
return c.provider.client.CopyToContainer(ctx, c.ID, filepath.Dir(containerFilePath), buffer, types.CopyToContainerOptions{})
}
// StartLogProducer will start a concurrent process that will continuously read logs
// from the container and will send them to each added LogConsumer
func (c *DockerContainer) StartLogProducer(ctx context.Context) error {
@ -401,8 +434,9 @@ func (n *DockerNetwork) Remove(ctx context.Context) error {
// DockerProvider implements the ContainerProvider interface
type DockerProvider struct {
client *client.Client
hostCache string
client *client.Client
hostCache string
defaultNetwork string // default container network
}
var _ ContainerProvider = (*DockerProvider)(nil)
@ -458,6 +492,31 @@ func (p *DockerProvider) BuildImage(ctx context.Context, img ImageBuildInfo) (st
// CreateContainer fulfills a request for a container without starting it
func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerRequest) (Container, error) {
var err error
// Make sure that bridge network exists
// In case it is disabled we will create reaper_default network
p.defaultNetwork, err = getDefaultNetwork(ctx, p.client)
if err != nil {
return nil, err
}
// If default network is not bridge make sure it is attached to the request
// as container won't be attached to it automatically
if p.defaultNetwork != Bridge {
isAttached := false
for _, net := range req.Networks {
if net == p.defaultNetwork {
isAttached = true
break
}
}
if !isAttached {
req.Networks = append(req.Networks, p.defaultNetwork)
}
}
exposedPortSet, exposedPortMap, err := nat.ParsePortSpecs(req.ExposedPorts)
if err != nil {
return nil, err
@ -537,6 +596,7 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque
ExposedPorts: exposedPortSet,
Labels: req.Labels,
Cmd: req.Cmd,
Hostname: req.Hostname,
}
// prepare mounts
@ -649,7 +709,7 @@ func (p *DockerProvider) RunContainer(ctx context.Context, req ContainerRequest)
// daemonHost gets the host or ip of the Docker daemon where ports are exposed on
// Warning: this is based on your Docker host setting. Will fail if using an SSH tunnel
// You can use the "TC_HOST" env variable to set this yourself
func (p *DockerProvider) daemonHost() (string, error) {
func (p *DockerProvider) daemonHost(ctx context.Context) (string, error) {
if p.hostCache != "" {
return p.hostCache, nil
}
@ -671,9 +731,13 @@ func (p *DockerProvider) daemonHost() (string, error) {
p.hostCache = url.Hostname()
case "unix", "npipe":
if inAContainer() {
ip, err := getGatewayIp()
ip, err := p.GetGatewayIP(ctx)
if err != nil {
return "", err
// fallback to getDefaultGatewayIP
ip, err = getDefaultGatewayIP()
if err != nil {
ip = "localhost"
}
}
p.hostCache = ip
} else {
@ -688,6 +752,12 @@ func (p *DockerProvider) daemonHost() (string, error) {
// CreateNetwork returns the object representing a new network identified by its name
func (p *DockerProvider) CreateNetwork(ctx context.Context, req NetworkRequest) (Network, error) {
var err error
// Make sure that bridge network exists
// In case it is disabled we will create reaper_default network
p.defaultNetwork, err = getDefaultNetwork(ctx, p.client)
if req.Labels == nil {
req.Labels = make(map[string]string)
}
@ -748,6 +818,27 @@ func (p *DockerProvider) GetNetwork(ctx context.Context, req NetworkRequest) (ty
return networkResource, err
}
func (p *DockerProvider) GetGatewayIP(ctx context.Context) (string, error) {
// Use a default network as defined in the DockerProvider
nw, err := p.GetNetwork(ctx, NetworkRequest{Name: p.defaultNetwork})
if err != nil {
return "", err
}
var ip string
for _, config := range nw.IPAM.Config {
if config.Gateway != "" {
ip = config.Gateway
break
}
}
if ip == "" {
return "", errors.New("Failed to get gateway IP from network settings")
}
return ip, nil
}
func inAContainer() bool {
// see https://github.com/testcontainers/testcontainers-java/blob/3ad8d80e2484864e554744a4800a81f6b7982168/core/src/main/java/org/testcontainers/dockerclient/DockerClientConfigUtils.java#L15
if _, err := os.Stat("/.dockerenv"); err == nil {
@ -756,7 +847,9 @@ func inAContainer() bool {
return false
}
func getGatewayIp() (string, error) {
// deprecated
// see https://github.com/testcontainers/testcontainers-java/blob/master/core/src/main/java/org/testcontainers/dockerclient/DockerClientConfigUtils.java#L46
func getDefaultGatewayIP() (string, error) {
// see https://github.com/testcontainers/testcontainers-java/blob/3ad8d80e2484864e554744a4800a81f6b7982168/core/src/main/java/org/testcontainers/dockerclient/DockerClientConfigUtils.java#L27
cmd := exec.Command("sh", "-c", "ip route|awk '/default/ { print $3 }'")
stdout, err := cmd.Output()
@ -769,3 +862,42 @@ func getGatewayIp() (string, error) {
}
return string(ip), nil
}
func getDefaultNetwork(ctx context.Context, cli *client.Client) (string, error) {
// Get list of available networks
networkResources, err := cli.NetworkList(ctx, types.NetworkListOptions{})
if err != nil {
return "", err
}
reaperNetwork := ReaperDefault
reaperNetworkExists := false
for _, net := range networkResources {
if net.Name == Bridge {
return Bridge, nil
}
if net.Name == reaperNetwork {
reaperNetworkExists = true
}
}
// Create a bridge network for the container communications
if !reaperNetworkExists {
_, err = cli.NetworkCreate(ctx, reaperNetwork, types.NetworkCreate{
Driver: Bridge,
Attachable: true,
Labels: map[string]string{
TestcontainerLabel: "true",
},
})
if err != nil {
return "", err
}
}
return reaperNetwork, nil
}

View File

@ -7,16 +7,17 @@ require (
github.com/Microsoft/go-winio v0.4.11 // indirect
github.com/Microsoft/hcsshim v0.8.6 // indirect
github.com/cenkalti/backoff v2.2.1+incompatible
github.com/containerd/containerd v1.4.1 // indirect
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc // indirect
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible
github.com/docker/docker v0.7.3-0.20190506211059-b20a14b54661
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible // indirect
github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible
github.com/docker/go-connections v0.4.0
github.com/docker/go-units v0.3.3 // indirect
github.com/gin-gonic/gin v1.6.3
github.com/go-redis/redis v6.15.8+incompatible
github.com/go-redis/redis v6.15.9+incompatible
github.com/go-sql-driver/mysql v1.5.0
github.com/gogo/protobuf v1.2.0 // indirect
github.com/google/uuid v1.1.1
github.com/google/uuid v1.1.2
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/mux v1.6.2 // indirect
github.com/kr/pretty v0.1.0 // indirect
@ -36,5 +37,3 @@ require (
gopkg.in/yaml.v2 v2.3.0
gotest.tools v0.0.0-20181223230014-1083505acf35
)
replace github.com/docker/docker => github.com/docker/engine v0.0.0-20190717161051-705d9623b7c1

View File

@ -8,6 +8,8 @@ github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXn
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/containerd/containerd v1.4.1 h1:pASeJT3R3YyVn+94qEPk0SnU1OQ20Jd/T+SPKy9xehY=
github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc h1:TP+534wVlf61smEIq1nwLLAjQVEK2EADoW3CX9AuT+8=
github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -15,8 +17,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible h1:dvc1KSkIYTVjZgHf/CTC2diTYC8PzhaA5sFISRfNVrE=
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/engine v0.0.0-20190717161051-705d9623b7c1 h1:pKV3lCoWunXtXfyRUcqYflvdaiFU3BMxHw5izMsYDhY=
github.com/docker/engine v0.0.0-20190717161051-705d9623b7c1/go.mod h1:3CPr2caMgTHxxIAZgEMd3uLYPDlRvPqCpyeRf6ncPcY=
github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible h1:SiUATuP//KecDjpOK2tvZJgeScYAklvyjfK8JZlU6fo=
github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
@ -25,8 +27,6 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.2 h1:88crIK23zO6TqlQBt+f9FrPJNKm9ZEr7qjp9vl/d5TM=
github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
@ -37,10 +37,8 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-redis/redis v6.15.7+incompatible h1:3skhDh95XQMpnqeqNftPkQD9jL9e5e36z/1SUm6dy1U=
github.com/go-redis/redis v6.15.7+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis v6.15.8+incompatible h1:BKZuG6mCnRj5AOaWJXoCgf6rqTYnYJLe4en2hxT7r9o=
github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI=
@ -54,8 +52,8 @@ github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaW
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
@ -110,10 +108,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho=
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=

View File

@ -70,6 +70,11 @@ func NewReaper(ctx context.Context, sessionID string, provider ReaperProvider, r
WaitingFor: wait.ForListeningPort(listeningPort),
}
// Attach reaper container to a requested network if it is specified
if p, ok := provider.(*DockerProvider); ok {
req.Networks = append(req.Networks, p.defaultNetwork)
}
c, err := provider.RunContainer(ctx, req)
if err != nil {
return nil, err

View File

@ -5,6 +5,7 @@ import (
"crypto/tls"
"errors"
"fmt"
"io"
"net"
"net/http"
"strconv"
@ -24,8 +25,13 @@ type HTTPStrategy struct {
Port nat.Port
Path string
StatusCodeMatcher func(status int) bool
ResponseMatcher func(body io.Reader) bool
UseTLS bool
AllowInsecure bool
TLSConfig *tls.Config // TLS config for HTTPS
Method string // http method
Body io.Reader // http request body
PollInterval time.Duration
}
// NewHTTPStrategy constructs a HTTP strategy waiting on port 80 and status code 200
@ -35,9 +41,13 @@ func NewHTTPStrategy(path string) *HTTPStrategy {
Port: "80/tcp",
Path: path,
StatusCodeMatcher: defaultStatusCodeMatcher,
ResponseMatcher: func(body io.Reader) bool { return true },
UseTLS: false,
TLSConfig: nil,
Method: http.MethodGet,
Body: nil,
PollInterval: defaultPollInterval(),
}
}
func defaultStatusCodeMatcher(status int) bool {
@ -63,8 +73,16 @@ func (ws *HTTPStrategy) WithStatusCodeMatcher(statusCodeMatcher func(status int)
return ws
}
func (ws *HTTPStrategy) WithTLS(useTLS bool) *HTTPStrategy {
func (ws *HTTPStrategy) WithResponseMatcher(matcher func(body io.Reader) bool) *HTTPStrategy {
ws.ResponseMatcher = matcher
return ws
}
func (ws *HTTPStrategy) WithTLS(useTLS bool, tlsconf ...*tls.Config) *HTTPStrategy {
ws.UseTLS = useTLS
if useTLS && len(tlsconf) > 0 {
ws.TLSConfig = tlsconf[0]
}
return ws
}
@ -73,6 +91,22 @@ func (ws *HTTPStrategy) WithAllowInsecure(allowInsecure bool) *HTTPStrategy {
return ws
}
func (ws *HTTPStrategy) WithMethod(method string) *HTTPStrategy {
ws.Method = method
return ws
}
func (ws *HTTPStrategy) WithBody(reqdata io.Reader) *HTTPStrategy {
ws.Body = reqdata
return ws
}
// WithPollInterval can be used to override the default polling interval of 100 milliseconds
func (ws *HTTPStrategy) WithPollInterval(pollInterval time.Duration) *HTTPStrategy {
ws.PollInterval = pollInterval
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 {
@ -99,49 +133,73 @@ func (ws *HTTPStrategy) WaitUntilReady(ctx context.Context, target StrategyTarge
return errors.New("Cannot use HTTP client on non-TCP ports")
}
portNumber := port.Int()
portString := strconv.Itoa(portNumber)
switch ws.Method {
case http.MethodGet, http.MethodHead, http.MethodPost,
http.MethodPut, http.MethodPatch, http.MethodDelete,
http.MethodConnect, http.MethodOptions, http.MethodTrace:
default:
if ws.Method != "" {
return fmt.Errorf("invalid http method %q", ws.Method)
}
ws.Method = http.MethodGet
}
address := net.JoinHostPort(ipAddress, portString)
tripper := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
ForceAttemptHTTP2: true,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: ws.TLSConfig,
}
var proto string
if ws.UseTLS {
proto = "https"
if ws.AllowInsecure {
if ws.TLSConfig == nil {
tripper.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
} else {
ws.TLSConfig.InsecureSkipVerify = true
}
}
} else {
proto = "http"
}
url := fmt.Sprintf("%s://%s%s", proto, address, ws.Path)
client := http.Client{Transport: tripper, Timeout: time.Second}
address := net.JoinHostPort(ipAddress, strconv.Itoa(port.Int()))
endpoint := 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:
return ctx.Err()
case <-time.After(ws.PollInterval):
req, err := http.NewRequestWithContext(ctx, ws.Method, endpoint, ws.Body)
if err != nil {
return err
}
resp, err := client.Do(req)
if err != nil || !ws.StatusCodeMatcher(resp.StatusCode) {
time.Sleep(100 * time.Millisecond)
if err != nil {
continue
}
break Retry
if ws.StatusCodeMatcher != nil && !ws.StatusCodeMatcher(resp.StatusCode) {
continue
}
if ws.ResponseMatcher != nil && !ws.ResponseMatcher(resp.Body) {
continue
}
if err := resp.Body.Close(); err != nil {
continue
}
return nil
}
}
return nil
}

View File

@ -17,8 +17,8 @@ type LogStrategy struct {
// additional properties
Log string
PollInterval time.Duration
Occurrence int
PollInterval time.Duration
}
// NewLogStrategy constructs a HTTP strategy waiting on port 80 and status code 200
@ -26,8 +26,8 @@ func NewLogStrategy(log string) *LogStrategy {
return &LogStrategy{
startupTimeout: defaultStartupTimeout(),
Log: log,
PollInterval: 100 * time.Millisecond,
Occurrence: 1,
PollInterval: defaultPollInterval(),
}
}

View File

@ -16,6 +16,7 @@ func ForSQL(port nat.Port, driver string, url func(nat.Port) string) *waitForSql
URL: url,
Driver: driver,
startupTimeout: defaultStartupTimeout(),
PollInterval: defaultPollInterval(),
}
}
@ -24,6 +25,7 @@ type waitForSql struct {
Driver string
Port nat.Port
startupTimeout time.Duration
PollInterval time.Duration
}
//Timeout sets the maximum waiting time for the strategy after which it'll give up and return an error
@ -32,13 +34,19 @@ func (w *waitForSql) Timeout(duration time.Duration) *waitForSql {
return w
}
//WithPollInterval can be used to override the default polling interval of 100 milliseconds
func (w *waitForSql) WithPollInterval(pollInterval time.Duration) *waitForSql {
w.PollInterval = pollInterval
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 60 seconds, it will return an error
func (w *waitForSql) WaitUntilReady(ctx context.Context, target StrategyTarget) (err error) {
ctx, cancel := context.WithTimeout(ctx, w.startupTimeout)
defer cancel()
ticker := time.NewTicker(time.Millisecond * 100)
ticker := time.NewTicker(w.PollInterval)
defer ticker.Stop()
port, err := target.MappedPort(ctx, w.Port)
@ -50,6 +58,7 @@ func (w *waitForSql) WaitUntilReady(ctx context.Context, target StrategyTarget)
if err != nil {
return fmt.Errorf("sql.Open: %v", err)
}
defer db.Close()
for {
select {
case <-ctx.Done():

View File

@ -22,3 +22,7 @@ type StrategyTarget interface {
func defaultStartupTimeout() time.Duration {
return 60 * time.Second
}
func defaultPollInterval() time.Duration {
return 100 * time.Millisecond
}