Skip to content

Commit

Permalink
Merge pull request #11 from thediveo/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
thediveo authored Jul 25, 2024
2 parents 0a0c1c2 + 89d0bc2 commit eb41583
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 5 deletions.
45 changes: 41 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
![build and test](https://github.com/thediveo/morbyd/workflows/build%20and%20test/badge.svg?branch=master)
![goroutines](https://img.shields.io/badge/go%20routines-not%20leaking-success)
[![Go Report Card](https://goreportcard.com/badge/github.com/thediveo/morbyd)](https://goreportcard.com/report/github.com/thediveo/morbyd)
![Coverage](https://img.shields.io/badge/Coverage-99.1%25-brightgreen)
![Coverage](https://img.shields.io/badge/Coverage-99.2%25-brightgreen)

`morbyd` is a thin layer on top of the standard Docker Go client to easily build
and run throw-away test Docker images and containers. And to easily run commands
Expand Down Expand Up @@ -35,11 +35,11 @@ elements, such as names, labels, and options.
- [Session.Run](https://pkg.go.dev/github.com/thediveo/morbyd#Session.Run) a new
container: options are in the
[run](https://pkg.go.dev/github.com/thediveo/morbyd/run) package, such as
[WithCommand](https://pkg.go.dev/github.com/thediveo/morbyd/run#WithCommand)...
[WithCommand](https://pkg.go.dev/github.com/thediveo/morbyd/run#WithCommand), ...
- [Container.Exec](https://pkg.go.dev/github.com/thediveo/morbyd#Container.Exec)ute
a command inside a container: options are in the
[exec](https://pkg.go.dev/github.com/thediveo/morbyd/exec) package, such as
[WithCombinedOutput](https://pkg.go.dev/github.com/thediveo/morbyd/exec#WithCombinedOutput)...
[WithCombinedOutput](https://pkg.go.dev/github.com/thediveo/morbyd/exec#WithCombinedOutput), ...
- [Session.CreateNetwork](https://pkg.go.dev/github.com/thediveo/morbyd#Session.CreateNetwork):
with options in the [net](https://pkg.go.dev/github.com/thediveo/morbyd/net)
and [bridge](https://pkg.go.dev/github.com/thediveo/morbyd/net/bridge),
Expand Down Expand Up @@ -113,11 +113,15 @@ func main() {
sess, _ := morbyd.NewSession(ctx, session.WithAutoCleaning("test.mytest="))
defer sess.Close(ctx)

// run a container and copy the container's combined output
// of stdout and stderr to our stdout.
cntr, _ := sess.Run(ctx, "busybox",
run.WithCommand("/bin/sh", "-c", "while true; do sleep 1; done"),
run.WithAutoRemove(),
run.WithCombinedOutput(os.Stdout))

// run a command inside the container and wait for this command
// to finish.
cmd, _ := cntr.Exec(ctx,
exec.WithCommand("/bin/sh", "-c", "echo \"Hellorld!\""),
exec.WithCombinedOutput(os.Stdout))
Expand Down Expand Up @@ -153,7 +157,7 @@ func main() {
cntr, _ := sess.Run(ctx, "busybox",
run.WithCommand("/bin/sh", "-c", `echo "DOH!" > index.html && httpd -f -p 1234`),
run.WithAutoRemove(),
run.WithPublishedPort("127.0.0.1:1234"))
run.WithPublishedPort("127.0.0.1:1234"))

svcAddrPort := container.PublishedPort("1234").Any().UnspecifiedAsLoopback().String()
req, _ := http.NewRequest(http.MethodGet, "http://"+svcAddrPort+"/", nil)
Expand All @@ -164,6 +168,39 @@ func main() {
}
```

### Dealing with Container Output

[safe.Buffer](https://pkg.go.dev/github.com/thediveo/morbyd/safe#Buffer) is the
concurrency-safe drop-in sibling to Go's
[bytes.Buffer](https://pkg.go.dev/bytes#Buffer): it is essential in unit tests
that reason about container output without setting off Go's race detector. The
reason is that container output is handled on background Go routines and
simultaneously polling an unguarded `bytes.Buffer` causes a race. All you need
to do is replace `bytes.Buffer` with `safe.Buffer` (which is just a thin
mutex'ed wrapper), for instance, in this test leveraging
[Gomega](https://onsi.github.io/gomega/):

```go
var buff safe.Buffer

// run a container that outputs a magic phrase and then
// keeps sleeping until the container gets terminated.
Expect(cntr.Exec(ctx,
exec.Command("/bin/sh", "-c", "echo \"**FOO!**\" 1>&2; while true; do sleep 1; done"),
exec.WithTTY(),
exec.WithCombinedOutput(
io.MultiWriter(
&buff, timestamper.New(GinkgoWriter))))).To(Succeed())

// ensure we got the magic phrase
Eventually(buff.String).Should(Equal("**FOO!**\r\n"))
```

[timestamper.New](https://pkg.go.dev/github.com/thediveo/[email protected]/timestamper#New)
returns a writer object implementing [io.Writer](https://pkg.go.dev/io#Writer)
that time stamps each line of output. It has proven useful in debugging tests
involving container output.

## Alternatives

Why `morbyd` when there are already other much bigger and long-time
Expand Down
40 changes: 39 additions & 1 deletion container_published_port_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ import (
context "context"
io "io"
"net/http"
"strings"

"github.com/thediveo/morbyd/net"
"github.com/thediveo/morbyd/run"
"github.com/thediveo/morbyd/session"
"github.com/thediveo/morbyd/timestamper"
Expand All @@ -42,8 +44,13 @@ var _ = Describe("published container ports", Ordered, func() {
`echo "DOH!" > index.html && httpd -v -f -p 1234`),
run.WithAutoRemove(),
run.WithCombinedOutput(timestamper.New(GinkgoWriter)),
// It's not possible (anymore) to publish the port both at the
// unspecified IP/IPv6 addresses, as well as on the IPv6 loopback
// address: this will fail with a bind error in the "userland
// proxy". Thus, we publish on a fixed port on IPv6 loopback to work
// around this restriction.
run.WithPublishedPort("1234"),
run.WithPublishedPort("[::1]:1234/tcp"),
run.WithPublishedPort("[::1]:1234:1234/tcp"),
))

svcAddrs := cntr.PublishedPort("1234")
Expand Down Expand Up @@ -73,4 +80,35 @@ var _ = Describe("published container ports", Ordered, func() {
Expect(string(Successful(io.ReadAll(resp.Body)))).To(Equal("DOH!\n"))
})

It("publishes ports on an IPv6 custom Docker network", func(ctx context.Context) {
sess := Successful(NewSession(ctx,
session.WithAutoCleaning("test.morbyd=container.portv6")))
DeferCleanup(func(ctx context.Context) { sess.Close(ctx) })

v6net, err := sess.CreateNetwork(ctx, "morbyd-v6notwork",
net.WithIPv6())
if err != nil && strings.Contains(err.Error(), "could not find an available, non-overlapping IPv6 address pool among the defaults") {
Skip("needs IPv6 pools for custom Docker networks")
}

By("spinning up an http serving busybox with published ports")
cntr := Successful(sess.Run(ctx,
"busybox",
run.WithCommand("/bin/sh", "-c",
`echo "DOH!" > index.html && httpd -v -f -p 1235`),
run.WithAutoRemove(),
run.WithCombinedOutput(timestamper.New(GinkgoWriter)),
run.WithNetwork(v6net.ID),
run.WithPublishedPort("[::1]:1235/tcp"),
))

svcAddrs := cntr.PublishedPort("1235")
Expect(svcAddrs).To(ConsistOf(
And(
HaveField("Network()", "tcp"),
MatchRegexp(`\[::1\]:\d+`),
),
))
})

})
9 changes: 9 additions & 0 deletions net/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ func WithIPv6() Opt {
}
}

// WithoutIPv6 disables IPv6 for the custom Docker network ... boo!
func WithoutIPv6() Opt {
return func(o *Options) error {
f := false
o.EnableIPv6 = &f
return nil
}
}

// WithLabel adds a label in “KEY=VALUE” to the custom Docker network.
func WithLabel(label string) Opt {
return func(o *Options) error {
Expand Down
8 changes: 8 additions & 0 deletions net/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ var _ = Describe("network options", func() {
Expect(netos.Labels).To(HaveKeyWithValue("bar", "baz"))
})

It("enables and disables IPv6", func() {
var opts Options
Expect(WithIPv6()(&opts)).To(Succeed())
Expect(opts.EnableIPv6).To(HaveValue(BeTrue()))
Expect(WithoutIPv6()(&opts)).To(Succeed())
Expect(opts.EnableIPv6).To(HaveValue(BeFalse()))
})

It("rejects invalid net options", func() {
var opts Options
Expect(WithLabels("=")(&opts)).To(HaveOccurred())
Expand Down

0 comments on commit eb41583

Please sign in to comment.