Files
Felis/internal/updater/gatherer_integration.go
T
flyemoji 7db57b9fff feat(updater): add VersionGatherer extraction core and CLI gather seam
Give the Runner a way to read each component's CURRENT version so it can be
compared against the release sources already wired. Three pure extractors turn
raw system text into an updates.Version, each fail-closed:

  - versionFromCLI      — a `<tool> --version` banner   (k3s, cloudflared)
  - versionFromImageRef — a container image tag         (felis-api)
  - versionFromJarName  — a proxy jar filename          (velocity)

sysGatherer routes each Topology component to the right extractor over an
injected seam; every path is exercised with a fake runner, mirroring how the
release sources are proven against httptest.

The load-bearing case is k3s: its Git tag "v1.36.2+k3s1" parses stable, but a
registry cannot store '+', so the same build ships as image tag "v1.36.2-k3s1",
which parses as a prerelease unless repaired. versionFromImageRef normalizes
"-k3sN"/"-rke2rN" back to "+", so an image read and a CLI read agree instead of
the image masquerading as a prerelease and being barred from comparison.

Honest runtime state after this slice — a green suite is not "the updater runs
against real infra": only the CLI seam (execRunner) is wired, so of the four
tracked components just cloudflared is live end to end (gatherable AND
Scheduled/appliable). k3s is CLI-gatherable but Notify-only. felis-api and
velocity are NOT yet runtime-gatherable: their producing seams — a k8s read of
the control-plane Deployment image, and an off-cluster jar inspection — are left
nil, so both surface an explicit "gather seam not wired" error rather than a
wrong version. felis-api self-update is therefore not functional yet.

Remaining integration (tracked in doc.go): the two producing seams, the concrete
Notifier (SMTP + in-game), the Applier (image bump, cloudflared swap), the
`felis update` CLI + CronJob entry point, and the runtime append of the Pinned
Minecraft fleet.
2026-07-05 04:05:56 +09:00

36 lines
1.7 KiB
Go

package updater
import (
"context"
"os/exec"
)
// This file is INTEGRATION-ONLY. execRunner shells out to the real k3s / cloudflared
// binaries; it cannot run on a box without them and is never exercised by the unit
// tests — the extraction logic it feeds (gatherer.go) is proven with a fake
// commandRunner. This mirrors internal/cfsetup, which keeps its ExecRunner apart from
// its unit-verified core.
// execRunner is the production commandRunner: it runs the tool and returns combined
// output (banners sometimes print to stderr), bounded by the caller's context.
type execRunner struct{}
func (execRunner) output(ctx context.Context, name string, args ...string) ([]byte, error) {
return exec.CommandContext(ctx, name, args...).CombinedOutput()
}
// NewSysGatherer builds the production VersionGatherer. The CLI seam (k3s / cloudflared
// `--version`) is wired and works on a real node. The felis-api pod-image read and the
// off-cluster Velocity jar inspection are the remaining integration seams; they are
// deliberately left nil, so those two components surface an explicit "gather seam not
// wired" error (which the Runner records and skips) rather than being planned against a
// wrong or zero version. Wiring them — a k8s client read of the control-plane
// Deployment's image, and however the operator exposes the proxy host — is the next
// integration step, tracked in doc.go. When the jar seam lands, revisit
// versionFromJarName's "-SNAPSHOT" handling: it reports a snapshot build as its stable
// core, which is harmless while this seam is nil and Velocity is Notify-only, but would
// suppress a legitimate "a stable is now out" notice once a real current flows.
func NewSysGatherer() VersionGatherer {
return sysGatherer{run: execRunner{}}
}