Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"path/filepath"
"regexp"
"strings"
"sync"
"testing"
"time"

Expand Down Expand Up @@ -214,11 +215,15 @@ func TestContainerReturnItsContainerID(t *testing.T) {
}

// testLogConsumer is a simple implementation of LogConsumer that logs to the test output.
// It is safe to use concurrently.
type testLogConsumer struct {
t *testing.T
t *testing.T
mx sync.Mutex
}

func (l *testLogConsumer) Accept(log Log) {
l.mx.Lock()
defer l.mx.Unlock()
l.t.Log(log.LogType + ": " + strings.TrimSpace(string(log.Content)))
}

Expand Down
3 changes: 3 additions & 0 deletions docs/features/creating_container.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ Inside each group, the hooks will be executed in the order they were defined.

It's important to notice that the `Readiness` of a container is defined by the wait strategies defined for the container. **This hook will be executed right after the `PostStarts` hook**. If you want to add your own readiness checks, you can do it by adding a `PostReadies` hook to the container request, which will execute your own readiness check after the default ones. That said, the `PostStarts` hooks don't warrant that the container is ready, so you should not rely on that.

!!!warning
Up to `v0.37.0`, the readiness hook included checks for all the exposed ports to be ready. This is not the case anymore, and the readiness hook only uses the wait strategies defined for the container to determine if the container is ready.

In the following example, we are going to create a container using all the lifecycle hooks, all of them printing a message when any of the lifecycle hooks is called:

<!--codeinclude-->
Expand Down
62 changes: 0 additions & 62 deletions lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"strings"
"time"

"github.com/cenkalti/backoff/v4"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/go-connections/nat"
Expand Down Expand Up @@ -213,71 +212,10 @@ var defaultLogConsumersHook = func(cfg *LogConsumerConfig) ContainerLifecycleHoo
}
}

func checkPortsMapped(exposedAndMappedPorts nat.PortMap, exposedPorts []string) error {
portMap, _, err := nat.ParsePortSpecs(exposedPorts)
if err != nil {
return fmt.Errorf("parse exposed ports: %w", err)
}

for exposedPort := range portMap {
// having entries in exposedAndMappedPorts, where the key is the exposed port,
// and the value is the mapped port, means that the port has been already mapped.
if _, ok := exposedAndMappedPorts[exposedPort]; ok {
continue
}

// check if the port is mapped with the protocol (default is TCP)
if strings.Contains(string(exposedPort), "/") {
return fmt.Errorf("port %s is not mapped yet", exposedPort)
}

// Port didn't have a type, default to tcp and retry.
exposedPort += "/tcp"
if _, ok := exposedAndMappedPorts[exposedPort]; !ok {
return fmt.Errorf("port %s is not mapped yet", exposedPort)
}
}

return nil
}

// defaultReadinessHook is a hook that will wait for the container to be ready
var defaultReadinessHook = func() ContainerLifecycleHooks {
return ContainerLifecycleHooks{
PostStarts: []ContainerHook{
func(ctx context.Context, c Container) error {
// wait until all the exposed ports are mapped:
// it will be ready when all the exposed ports are mapped,
// checking every 50ms, up to 1s, and failing if all the
// exposed ports are not mapped in 5s.
dockerContainer := c.(*DockerContainer)

b := backoff.NewExponentialBackOff()

b.InitialInterval = 50 * time.Millisecond
b.MaxElapsedTime = 5 * time.Second
b.MaxInterval = time.Duration(float64(time.Second) * backoff.DefaultRandomizationFactor)

err := backoff.RetryNotify(
func() error {
jsonRaw, err := dockerContainer.inspectRawContainer(ctx)
if err != nil {
return err
}

return checkPortsMapped(jsonRaw.NetworkSettings.Ports, dockerContainer.exposedPorts)
},
b,
func(err error, _ time.Duration) {
dockerContainer.logger.Printf("All requested ports were not exposed: %v", err)
},
)
if err != nil {
return fmt.Errorf("all exposed ports, %s, were not mapped in 5s: %w", dockerContainer.exposedPorts, err)
}

return nil
},
// wait for the container to be ready
func(ctx context.Context, c Container) error {
dockerContainer := c.(*DockerContainer)
Expand Down
67 changes: 0 additions & 67 deletions lifecycle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -475,73 +475,6 @@ func TestMergePortBindings(t *testing.T) {
}
}

func TestPortMappingCheck(t *testing.T) {
makePortMap := func(ports ...string) nat.PortMap {
out := make(nat.PortMap)
for _, port := range ports {
// We don't care about the actual binding in this test
out[nat.Port(port)] = nil
}
return out
}

tests := map[string]struct {
exposedAndMappedPorts nat.PortMap
exposedPorts []string
expectError bool
}{
"no-protocol": {
exposedAndMappedPorts: makePortMap("1024/tcp"),
exposedPorts: []string{"1024"},
},
"protocol": {
exposedAndMappedPorts: makePortMap("1024/tcp"),
exposedPorts: []string{"1024/tcp"},
},
"protocol-target-port": {
exposedAndMappedPorts: makePortMap("1024/tcp"),
exposedPorts: []string{"1024:1024/tcp"},
},
"target-port": {
exposedAndMappedPorts: makePortMap("1024/tcp"),
exposedPorts: []string{"1024:1024"},
},
"multiple-ports": {
exposedAndMappedPorts: makePortMap("1024/tcp", "1025/tcp", "1026/tcp"),
exposedPorts: []string{"1024", "25:1025/tcp", "1026:1026"},
},
"only-ipv4": {
exposedAndMappedPorts: makePortMap("1024/tcp"),
exposedPorts: []string{"0.0.0.0::1024/tcp"},
},
"no-mapped-ports": {
exposedAndMappedPorts: makePortMap(),
exposedPorts: []string{"1024"},
expectError: true,
},
"wrong-mapped-port": {
exposedAndMappedPorts: makePortMap("1023/tcp"),
exposedPorts: []string{"1024"},
expectError: true,
},
"subset-mapped-ports": {
exposedAndMappedPorts: makePortMap("1024/tcp", "1025/tcp"),
exposedPorts: []string{"1024", "1025", "1026"},
expectError: true,
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
err := checkPortsMapped(tt.exposedAndMappedPorts, tt.exposedPorts)
if tt.expectError {
require.Error(t, err)
return
}
require.NoError(t, err)
})
}
}

func TestLifecycleHooks(t *testing.T) {
tests := []struct {
name string
Expand Down
Loading