Content tagged go
Docker is ubiquitous in many projects and therefore it may be useful to dig into more detail about its inner workings. Arguably those aren't too complicated to build a smallish program that does the essentials in a few hours.
The CodeCrafters challenges focus on exactly this kind of idea, taking an existing tool and rebuilding it from scratch. Since they're currently in Early Access, I've only had the opportunity to try out the Docker and Redis challenges so far, but I thought maybe a few insights from them would be good to share.
Part of the challenge is to run the entrypoint of a container; using Go
it's actually fairly easy to run external programs. Using the
os/exec package is straightforward, even
redirecting I/O is easy enough by looking at the
Cmd structure a bit closer and
assigning values to the Stdin
, Stdout
and Stderr
fields. Also the
exit status can be easily gotten from the error
return value by
checking for ExitError
(only if it was not successful, that is,
non-zero):
if err = cmd.Run(); err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
...
}
}
Interestingly enough the SysProcAttr
field exposes some functionality
that is a bit more difficult to use in, say, C. While using the
syscall package is
possible, it's mostly easier to assign a few values in that field
instead, using the definition of the
SysProcAttr structure
itself.
Later on there's also the need to parse some JSON - that's again easily
done with the standard library, using
encoding/json
, in
particular
Unmarshal
to a
map[string]interface{}
(in case we just want to grab a top-level entry
in a JSON object), or to a pointer of a custom class using structure
tags like so:
type Foo struct {
Bars []Bar `json:"bars"`
}
type Bar struct {
Baz string `json:"baz"`
}
...
foo := Foo{}
if err := json.Unmarshal(body, &foo); err != nil {
panic(err)
}
for _, bar := range foo.Bars {
println(bar.Baz)
}
The Redis challenge is comparatively more contained to just using
standard library tools, the most interesting thing I've noticed was that
there's now a concurrency-friendly map implementation called
sync.Map
, so no external
synchronization primitive is needed.
What else helped is the redis-cli
tool, though I had to find out for myself that it doesn't interpret the
specification very strictly, in fact, just about everything returned
from the server response will be printed, even when not valid according
to the spec.
Overall the biggest challenge here might be to accurately parse the command input and deal with expiration (I simply chose a lazy approach there, instead of clearing out the map on a timer I suppose - this will of course not be memory-friendly long-term, but for implementing a very simple Redis server it's more than enough to pass all tests).