Skip to content

Commit

Permalink
Merge pull request #11 from orlangure/error-handling-improvement
Browse files Browse the repository at this point in the history
Support forwarding logs
  • Loading branch information
orlangure authored Mar 27, 2020
2 parents 1d2eb22 + 344361c commit 9993ed8
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 0 deletions.
2 changes: 2 additions & 0 deletions container.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ type Container struct {
ID string
Host string
Ports NamedPorts

onStop func() error
}

// Address is a convenience function that returns host:port that can be used to
Expand Down
14 changes: 14 additions & 0 deletions docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package gnomock
import (
"context"
"fmt"
"io"
"io/ioutil"
"strconv"
"time"
Expand Down Expand Up @@ -142,6 +143,19 @@ func (d *docker) boundNamedPorts(json types.ContainerJSON, namedPorts NamedPorts
return boundNamedPorts, nil
}

func (d *docker) readLogs(ctx context.Context, id string) (io.ReadCloser, error) {
logsOptions := types.ContainerLogsOptions{
ShowStderr: true, ShowStdout: true, Follow: true,
}

rc, err := d.client.ContainerLogs(ctx, id, logsOptions)
if err != nil {
return nil, fmt.Errorf("can't read logs: %w", err)
}

return rc, nil
}

func (d *docker) stopContainer(ctx context.Context, id string) error {
stopTimeout := defaultStopTimeout

Expand Down
47 changes: 47 additions & 0 deletions gnomock.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ package gnomock
import (
"context"
"fmt"
"io"
"strings"
"time"

"github.com/docker/docker/pkg/stdcopy"
"golang.org/x/sync/errgroup"
)

const defaultTag = "latest"
Expand Down Expand Up @@ -42,6 +46,17 @@ func Start(image string, ports NamedPorts, opts ...Option) (c *Container, err er
return nil, fmt.Errorf("can't start container: %w", err)
}

logReader, err := cli.readLogs(context.Background(), c.ID)
if err != nil {
return nil, fmt.Errorf("can't setup log forwarding: %w", err)
}

g := &errgroup.Group{}

g.Go(copy(config.logWriter, logReader))

c.onStop = closeLogReader(logReader, g)

waitCtx, cancelWait := context.WithTimeout(config.ctx, config.waitTimeout)
defer cancelWait()

Expand All @@ -58,6 +73,33 @@ func Start(image string, ports NamedPorts, opts ...Option) (c *Container, err er
return c, nil
}

func copy(dst io.Writer, src io.Reader) func() error {
return func() error {
_, err := stdcopy.StdCopy(dst, dst, src)
if err != nil && err != io.EOF {
return err
}

return nil
}
}

func closeLogReader(logReader io.ReadCloser, g *errgroup.Group) func() error {
return func() error {
err := logReader.Close()
if err != nil {
return err
}

err = g.Wait()
if err != nil {
return err
}

return nil
}
}

// StartPreset creates a container using the provided Preset. The Preset
// provides its own Options to configure Gnomock container. Usually this is
// enough, but it is still possible to extend/override Preset options with new
Expand Down Expand Up @@ -94,6 +136,11 @@ func Stop(c *Container) error {
return fmt.Errorf("can't stop container: %w", err)
}

err = c.onStop()
if err != nil {
return fmt.Errorf("can't perform last cleanup: %w", err)
}

return nil
}

Expand Down
42 changes: 42 additions & 0 deletions gnomock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"testing"
Expand Down Expand Up @@ -125,6 +126,47 @@ func TestGnomock_initError(t *testing.T) {
require.True(t, errors.Is(err, errNope))
}

func TestGnomock_cantStart(t *testing.T) {
t.Parallel()

container, err := gnomock.Start(
"docker.io/orlangure/noimage",
gnomock.DefaultTCP(goodPort80),
)

defer func() { require.NoError(t, gnomock.Stop(container)) }()

require.Error(t, err)
}

func TestGnomock_withLogWriter(t *testing.T) {
t.Parallel()

r, w := io.Pipe()

container, err := gnomock.Start(
testImage, gnomock.DefaultTCP(goodPort80),
gnomock.WithLogWriter(w),
)
require.NoError(t, err)

signal := make(chan struct{})

go func() {
defer close(signal)

log, err := ioutil.ReadAll(r)
require.NoError(t, err)
require.Equal(t, "starting with env1 = '', env2 = ''\n", string(log))
}()

require.NoError(t, gnomock.Stop(container))

require.NoError(t, w.Close())
<-signal
require.NoError(t, r.Close())
}

func healthcheck(c *gnomock.Container) error {
err := callRoot(fmt.Sprintf("http://%s/", c.Address("web80")))
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/stretchr/testify v1.4.0
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 // indirect
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v2 v2.2.4 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
12 changes: 12 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package gnomock

import (
"context"
"io"
"io/ioutil"
"time"
)

Expand Down Expand Up @@ -77,6 +79,14 @@ func WithEnv(env string) Option {
}
}

// WithLogWriter sets the target where to write container logs. This can be
// useful for debugging
func WithLogWriter(w io.Writer) Option {
return func(o *options) {
o.logWriter = w
}
}

// HealthcheckFunc defines a function to be used to determine container health.
// It receives a host and a port, and returns an error if the container is not
// ready, or nil when the container can be used. One example of HealthcheckFunc
Expand Down Expand Up @@ -105,6 +115,7 @@ type options struct {
startTimeout time.Duration
waitTimeout time.Duration
env []string
logWriter io.Writer
}

func buildConfig(opts ...Option) *options {
Expand All @@ -115,6 +126,7 @@ func buildConfig(opts ...Option) *options {
healthcheckInterval: defaultHealthcheckInterval,
startTimeout: defaultStartTimeout,
waitTimeout: defaultWaitTimeout,
logWriter: ioutil.Discard,
}

for _, opt := range opts {
Expand Down

0 comments on commit 9993ed8

Please # to comment.