concepts, only once i've actually needed them

Learning Go

Not working through a Go book here. Each thing on this page is a concept I only actually understood because I needed it to read or write real OpenEverest code. Haven't hit it in the codebase yet, it's not on here.

interfaces as contracts, not inheritance

First Go idiom I actually got by reading real code instead of a tutorial: internal/server/handlers/handler.go in the OpenEverest repo.

The API server processes every request through three steps: check it’s well-formed, check the user’s allowed to do it, then actually talk to Kubernetes. In a language with classes I’d reach for inheritance. Go does it differently. One small Handler interface, three separate structs (valhandler, rbachandler, k8shandler) that each implement it. Every handler knows its own job and knows how to call SetNext to hand off to whatever’s after it. None of them know the full chain, just the next one in line.

type Handler interface {
    Handle(ctx echo.Context) error
    SetNext(next Handler)
}

This is basically what got me to stop thinking of Go interfaces as “class hierarchy, different syntax.” A Go interface isn’t something you inherit from, it’s just a promise: this type has these methods. Any struct with Handle and SetNext satisfies Handler, no implements Handler written anywhere. Which is exactly what makes it possible to swap valhandler out in a test without touching the other two.

the toolkit, before touching any code

Checked go.mod before writing a single line, because what a Go codebase depends on tells you what patterns you’re actually about to be reading. OpenEverest’s:

None of this is OpenEverest-specific, it’s the same stuff you’d find across a big chunk of the Kubernetes ecosystem. So understanding it here isn’t wasted if I ever touch a different project.

deleting code is a real skill, and go won’t help you with it

First actual code fix, not docs, in plugin-audit, a small plugin in the org. An API endpoint it depended on got removed, so its login function was broken. Fixing the URL and the request shape was the easy part. The harder, more interesting part was noticing that fixing it also made a whole chunk of other code pointless: the old response didn’t tell you when a token expired, so there was a whole block decoding the JWT by hand to guess at it. The new response just says expires_in directly. That block, and the helper function it called, could just go.

Here’s the thing that actually taught me something about Go specifically: the compiler will yell at you for an unused import or an unused variable, but it will say absolutely nothing about an unused function. decodeJWTSegment would have sat there dead forever, still exported enough to look intentional, if I hadn’t gone and checked by hand whether anything else in the package still called it. In a lot of other languages a linter catches this by default. In Go, checking is on you.

// nothing complains if this becomes unreachable
func decodeJWTSegment(seg string) ([]byte, error) {
    return base64.RawURLEncoding.DecodeString(seg)
}

Deleted it, deleted the now-unused encoding/base64 import (that one, the compiler does catch), rebuilt, and only trusted it once I’d actually run the binary against a live server and watched it authenticate successfully in the logs. Reading the diff and believing it compiles is not the same as knowing it works.

a fake client, and a test that lies if you let it

Fixing a bug in provider-percona-server-mongodb where a backup’s completion time was faked instead of read from the real operator status. First time writing a test that needed a Kubernetes object in it, and controller-runtime ships exactly the tool for that: sigs.k8s.io/controller-runtime/pkg/client/fake. Build a scheme, register the CRDs it needs to know about, hand it some seed objects, and it behaves like a real API server for anything your code does through the normal client.Client interface, no cluster required.

fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(objs...).Build()

The test passed first try, which should have made me suspicious rather than satisfied. A test that passes doesn’t tell you it would fail if the bug came back, only that it doesn’t fail right now. So I went and put the actual bug back, temporarily, by hand, reran the exact same test, and watched it fail with the wrong timestamp, the real proof that it was checking something instead of just running through motions. Then undid that and moved on.

Along the way, a genuinely surprising gotcha: two metav1.Time values, one before my code touched the fake client and one after, weren’t equal, down to the nanosecond, they were only equal to the second. The fake client round-trips objects through the same wire serialization a real API server would, and Kubernetes’ time format is RFC3339, which has no sub-second resolution. My code was fine; my test was comparing a value with nanoseconds against one that had them stripped in transit. Fixed by truncating my expected value the same way before comparing:

started := metav1.NewTime(metav1.Now().Truncate(time.Second))

Small thing, but it’s the kind of small thing that would have had me doubting a correct fix for the wrong reason if I hadn’t chased down exactly why the numbers didn’t match instead of just fudging the assertion.

why the field is a pointer, not just a time.Time

Same fix, different lesson. The field I was populating is typed *metav1.Time, a pointer, not metav1.Time plain. First instinct was that this was just Kubernetes being Kubernetes. It’s not incidental, it’s the only way the type can honestly represent “this hasn’t happened yet.”

A backup that’s still running has no completion time. If the field were a plain metav1.Time, there’d be no way to write that down, the zero value of a struct still is a value, 0001-01-01T00:00:00Z, and nothing stops you from serializing it and someone reading it as a real, if weird, timestamp. A nil pointer can’t be confused for a date that far in the past. It can only mean “not set.” That’s the whole reason StartedAt and CompletedAt are both pointers here: the difference between “hasn’t happened” and “happened at a specific, knowable moment” has to be representable, and only one of those two field shapes actually can.

Once I saw it that way, a pattern clicked that I’d been vaguely noticing without naming: in Kubernetes API types, a pointer to a small value almost always means “this is optional and its absence is meaningful,” not “this is big enough to want to avoid copying it.”

reading the vendored source instead of guessing the field name

Needed the exact field names on PerconaServerMongoDBBackupStatus, the actual struct the real operator uses, not our own CRD. Didn’t guess, and didn’t rely on documentation either, both are one refactor away from being wrong. Found the vendored copy in Go’s own module cache and read the struct directly:

go list -m -f '' github.com/percona/percona-server-mongodb-operator

That command prints the exact on-disk path Go resolved for that dependency at the version this project actually pins, then it’s just grep or an editor from there. Works for any Go dependency, not just this one, and it’s more trustworthy than any comment or doc page, because it’s literally the compiler’s own source of truth for what that type looks like right now, not what it looked like when someone last wrote it down.

Adding to this as I actually run into the next thing, not before.