morbyd: a thin layer to easily build and run throw-away test containers, etc. in tests

48 views
Skip to first unread message

TheDiveO

unread,
Feb 2, 2024, 12:11:29 PM2/2/24
to golang-nuts
https://github.com/thediveo/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 running commands inside them. It features a function option API to keep the slightly excessive Docker API option parameters at bay. (And large test coverage FWIW).

You might ask: why, when there's ory/dockertest/v3?

Because their Ory's proprietary Docker client (predating the canonical one) is incompatible with the Docker daemon's 100 CONTINUE header when streaming container and command stdout/stderr. I've reported this, but did not got any response at all, so I finally cut my losses and wrote "morbyd" to be able to move on. The proper Docker client allows me to easily dump and analyse build and container output when tests go south.

Below is how morbyd feels, compared to ory/dockertest/v3, to give you an idea which API design might be better suited for you (or not):

dockertest (using Gomega for assertions):

Expect(pool.Client.BuildImage(docker.BuildImageOptions{
Name:       img.Name,
ContextDir: "./test/_kindisch", // sorry, couldn't resist the pun.
Dockerfile: "Dockerfile",
BuildArgs: []docker.BuildArg{
{Name: "KINDEST_BASE_TAG", Value: test.KindestBaseImageTag},
},
OutputStream: io.Discard,
})).To(Succeed())
providerCntr := Successful(pool.RunWithOptions(
&dockertest.RunOptions{
Name:       kindischName,
Repository: img.Name,
Privileged: true,
Mounts: []string{
"/var", // well, this actually is an unnamed volume
"/dev/mapper:/dev/mapper",
"/lib/modules:/lib/modules:ro",
},
Tty: true,
}, func(hc *docker.HostConfig) {
hc.Init = false
hc.Tmpfs = map[string]string{
"/tmp": "",
"/run": "",
}
hc.Devices = []docker.Device{
{PathOnHost: "/dev/fuse"},
}
}))

morbyd (also using Gomega for assertions):

Expect(sess.BuildImage(ctx, "./test/_kindisch",
build.WithTag(img.Name),
build.WithBuildArg("KINDEST_BASE_TAG="+test.KindestBaseImageTag),
build.WithOutput(timestamper.New(GinkgoWriter)))).
Error().NotTo(HaveOccurred())
providerCntr := Successful(sess.Run(ctx, img.Name,
run.WithName(kindischName),
run.WithAutoRemove(),
run.WithPrivileged(),
run.WithSecurityOpt("label=disable"),
run.WithCgroupnsMode("private"),
run.WithVolume("/var"),
run.WithVolume("/dev/mapper:/dev/mapper"),
run.WithVolume("/lib/modules:/lib/modules:ro"),
run.WithTmpfs("/tmp"),
run.WithTmpfs("/run"),
run.WithDevice("/dev/fuse"),
run.WithCombinedOutput(timestamper.New(GinkgoWriter))))


Some of these options are not simply writing their arg in the same-name struct field, but instead are parsing things, preferably in the same way as the Docker CLI does. So the transition from CLI args to unit test args should be much smoother without having to dig deep into the Docker API parameter details.

While I tried to strive for more option completeness than just my own minimal viable proof, there surely a bits and pieces missing. In case morbyd might be interesting for you, I would be glad to hear from you and also missing pieces; and preferably a PR if you would be so kind.



Reply all
Reply to author
Forward
0 new messages