Skip to content

Commit

Permalink
Merge pull request #162 from testcontainers/fix/image-no-found-hangs
Browse files Browse the repository at this point in the history
Fix infinite loop caused by "image not found"
  • Loading branch information
gianarb authored Mar 3, 2020
2 parents 13a946d + 5279bbb commit 59c8ff6
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 15 deletions.
42 changes: 28 additions & 14 deletions docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"context"
"encoding/binary"
"fmt"
"github.com/cenkalti/backoff"
"github.com/docker/docker/errdefs"
"io"
"io/ioutil"
"log"
Expand All @@ -14,7 +16,6 @@ import (
"strings"
"time"

"github.com/cenkalti/backoff"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/mount"
Expand Down Expand Up @@ -500,22 +501,11 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque
if req.RegistryCred != "" {
pullOpt.RegistryAuth = req.RegistryCred
}
var pull io.ReadCloser
err := backoff.Retry(func() error {
var err error
pull, err = p.client.ImagePull(ctx, tag, pullOpt)
return err
}, backoff.NewExponentialBackOff())
if err != nil {
return nil, err
}
defer pull.Close()

// download of docker image finishes at EOF of the pull request
_, err = ioutil.ReadAll(pull)
if err != nil {
if err := p.attemptToPullImage(ctx, tag, pullOpt); err != nil {
return nil, err
}

} else {
return nil, err
}
Expand Down Expand Up @@ -592,6 +582,30 @@ func (p *DockerProvider) CreateContainer(ctx context.Context, req ContainerReque
return c, nil
}

//attemptToPullImage tries to pull the image while respecting the ctx cancellations.
// Besides, if the image cannot be pulled due to ErrorNotFound then no need to retry but terminate immediately.
func (p *DockerProvider) attemptToPullImage(ctx context.Context, tag string, pullOpt types.ImagePullOptions) error {
var (
err error
pull io.ReadCloser
)
err = backoff.Retry(func() error {
pull, err = p.client.ImagePull(ctx, tag, pullOpt)
if _, ok := err.(errdefs.ErrNotFound); ok {
return backoff.Permanent(err)
}
return err
}, backoff.WithContext(backoff.NewExponentialBackOff(), ctx))
if err != nil {
return err
}
defer pull.Close()

// download of docker image finishes at EOF of the pull request
_, err = ioutil.ReadAll(pull)
return err
}

// RunContainer takes a RequestContainer as input and it runs a container via the docker sdk
func (p *DockerProvider) RunContainer(ctx context.Context, req ContainerRequest) (Container, error) {
c, err := p.CreateContainer(ctx, req)
Expand Down
39 changes: 38 additions & 1 deletion docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package testcontainers

import (
"context"
"errors"
"fmt"
"net/http"
"path/filepath"
"testing"
"time"

"github.com/docker/docker/errdefs"

"github.com/docker/docker/api/types/volume"

"database/sql"
Expand Down Expand Up @@ -36,7 +39,7 @@ func TestContainerAttachedToNewNetwork(t *testing.T) {
networkName,
},
NetworkAliases: map[string][]string{
networkName: []string{
networkName: {
"alias1", "alias2", "alias3",
},
},
Expand Down Expand Up @@ -1180,3 +1183,37 @@ func TestContainerWithTmpFs(t *testing.T) {
t.Fatalf("File %s should exist, expected return code 0, got %v", path, c)
}
}

func TestContainerNonExistentImage(t *testing.T) {
t.Run("if the image not found don't propagate the error", func(t *testing.T) {
_, err := GenericContainer(context.Background(), GenericContainerRequest{
ContainerRequest: ContainerRequest{
Image: "postgres:nonexistent-version",
SkipReaper: true,
},
Started: true,
})

var nf errdefs.ErrNotFound
if !errors.As(err, &nf) {
t.Fatalf("the error should have bee an errdefs.ErrNotFound: %v", err)
}
})

t.Run("the context cancellation is propagated to container creation", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
_, err := GenericContainer(ctx, GenericContainerRequest{
ContainerRequest: ContainerRequest{
Image: "postgres:latest",
WaitingFor: wait.ForLog("log"),
SkipReaper: true,
},
Started: true,
})
if !errors.Is(err, ctx.Err()) {
t.Fatalf("err should be a ctx cancelled error %v", err)
}
})

}

0 comments on commit 59c8ff6

Please sign in to comment.