felis-api's coord was the placeholder "felis/felis", which resolves against nothing on real GitHub. It is now MliroLirrorsIngenuity/Felis — the same slug deploy/bootstrap.sh clones from — so update reporting for the control plane itself is live rather than parked. That repo is private today, so the github source gained an optional token, read from FELIS_GITHUB_TOKEN: the variable bootstrap already needs, so an operator sets one value once. It comes from the environment and is never compiled in. A constant would be committed to the very repository it protects, ship inside every felis binary where strings(1) recovers it, reach every node the image is imported onto, and need a rebuild and a redeploy to rotate. Empty stays the correct posture for the other tracked components — k3s and cloudflared are public — and an empty token sends no Authorization header at all rather than an empty one. GitHub answers 404, not 401 or 403, for a private repo the caller cannot see, so "no token" and "no stable release published yet" arrive as the same status. On an unauthenticated 404 the error now names both causes and the variable that fixes the actionable one. With a token already set that hint would be wrong, so it is suppressed. Tests pin both halves: the Bearer header is sent only when the token is set, and the diagnostic names the variable only when it is not. doc.go's CAVEATS bullet still described the coord as a placeholder and the component as "dark at runtime". Both were true only until this change; it now records the real condition, which is that the component resolves like the others but needs a credential while the repo is private.
49 lines
1.9 KiB
Go
49 lines
1.9 KiB
Go
package updater
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"felis.lolicon.best/internal/updates"
|
|
)
|
|
|
|
// TestTopologyEncodesPolicies pins the user's stated decisions: which components Felis
|
|
// tracks and how aggressively. A regression here would silently change what Felis is
|
|
// allowed to auto-apply.
|
|
func TestTopologyEncodesPolicies(t *testing.T) {
|
|
want := map[string]Spec{
|
|
"felis-api": {Name: "felis-api", Policy: updates.PolicyScheduled, Manageable: true, Source: sourceGitHub, Coord: "MliroLirrorsIngenuity/Felis"},
|
|
"k3s": {Name: "k3s", Policy: updates.PolicyNotify, Manageable: false, Source: sourceGitHub, Coord: "k3s-io/k3s"},
|
|
"cloudflared": {Name: "cloudflared", Policy: updates.PolicyScheduled, Manageable: true, Source: sourceGitHub, Coord: "cloudflare/cloudflared"},
|
|
"velocity": {Name: "velocity", Policy: updates.PolicyNotify, Manageable: false, Source: sourcePaperMC, Coord: "velocity"},
|
|
}
|
|
got := Topology()
|
|
if len(got) != len(want) {
|
|
t.Fatalf("Topology has %d specs, want %d", len(got), len(want))
|
|
}
|
|
for _, s := range got {
|
|
w, ok := want[s.Name]
|
|
if !ok {
|
|
t.Errorf("unexpected component %q in topology", s.Name)
|
|
continue
|
|
}
|
|
if s != w {
|
|
t.Errorf("component %q = %+v, want %+v", s.Name, s, w)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestTopologyPinsMinecraft proves the pin is expressed as ABSENCE: no component in
|
|
// the static topology is Pinned, because Minecraft is never force-tracked here — it
|
|
// is appended from the live fleet at runtime. And nothing off-cluster is proposed for
|
|
// self-apply: velocity must be non-manageable (Notify at most, never Apply).
|
|
func TestTopologyPinsMinecraft(t *testing.T) {
|
|
for _, s := range Topology() {
|
|
if s.Policy == updates.PolicyPinned {
|
|
t.Errorf("component %q is Pinned in the static topology; Minecraft pins belong to the runtime fleet, not here", s.Name)
|
|
}
|
|
if s.Name == "velocity" && s.Manageable {
|
|
t.Error("velocity is off-cluster and must be non-manageable (Notify only)")
|
|
}
|
|
}
|
|
}
|