Skip to content

Latest commit

 

History

History
83 lines (64 loc) · 4.14 KB

File metadata and controls

83 lines (64 loc) · 4.14 KB

Notable rationale of func-e

Why do we have so many environment variables for file locations?

Recently, func-e is used as a library, which means its default locations need to support layouts that are non-default. We also want to be able to predict the log directory when running in Docker instead of using a timestamp in the path.

This results in several config locations like this:

Environment Variable Default Path API Option
FUNC_E_CONFIG_HOME ~/.config/func-e api.ConfigHome()
FUNC_E_DATA_HOME ~/.local/share/func-e api.DataHome()
FUNC_E_STATE_HOME ~/.local/state/func-e api.StateHome()
FUNC_E_RUNTIME_DIR /tmp/func-e-${UID} api.RuntimeDir()
FUNC_E_RUN_ID auto-generated api.RunID()

These are conventional to XDG, which makes it easier to explain to people. Also, XDS conventions are used by Prometheus and block/goose, so will be familiar to some.

In summary, XDS conventions allow dependents like Envoy AI Gateway to brand their own directories and co-mingle its configuration and logs with those of func-e when it runs Envoy (the gateway process). It also allows Docker to export FUNC_E_RUN_ID=0 to aid in location of key files.

Why Tools.mk?

Tools.mk lists Go tools (linters, hugo, nfpm) with pinned versions so make can run them via go run package@version without a platform-specific install. Tool deps stay out of go.mod, which matters because func-e is imported as a library.

We previously used go tool with a separate tools/go.mod, but ran into two problems. First, all tools share one dependency graph, so hugo, golangci-lint, and nfpm can revlock each other. Second, Go rejects -modfile in workspace mode, forcing GOWORK=off into every tool invocation.

go run resolves each tool independently, avoiding both problems. The tradeoff is no go.sum lock for tool versions and only working with make.

Why internal/test/httptest?

Go's net/http/httptest.NewServer listens on loopback TCP. Network I/O is not durably blocking, so goroutines stuck on TCP prevent a synctest bubble from becoming idle. The fake clock never advances, and any time.After or time.Sleep in the code under test hangs.

Our httptest.NewServer replaces the TCP listener with net.Pipe, following the pattern in Go's TestTLSServerWithoutTLSConn and TransportCancelRequestBeforeResponseHeaders tests. Pipe operations block on channels, which are durably blocking, so synctest can see when the bubble is idle and advance the clock. Retrofitting httptest.NewServer keeps test practice familiar while avoiding the blocking behavior.

We also provide httptest.HTTPClient, which runs a handler synchronously in the caller's goroutine with no I/O at all. Tests who need *http.Client, but don't need a real server can use this instead.

Why "dev-latest" instead of a flag?

func-e is embedded in CI systems and tools like Envoy AI Gateway that may only allow version overrides, not flags. A version string works everywhere ENVOY_VERSION is accepted, including the Go API.

dev installs on demand like all other versions. Once pulled, it stays put. This keeps CI runs stable and reproducible without network access on every invocation.

dev-latest is the explicit refresh trigger. Without it, a cached dev install would never update. CI pipelines and tools that re-warm caches need a way to pull the latest build without manual intervention.