diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e2ddc1f..cd8da21 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,12 @@ jobs: with: go-version-file: go.mod + - name: gofmt + run: | + unformatted=$(gofmt -l .) + if [ -n "$unformatted" ]; then + echo "gofmt needed on:"; echo "$unformatted"; exit 1 + fi - run: go vet ./... - run: go test ./... diff --git a/cmd/felis/backupnow.go b/cmd/felis/backupnow.go index 435e21d..a7e462f 100644 --- a/cmd/felis/backupnow.go +++ b/cmd/felis/backupnow.go @@ -51,7 +51,7 @@ func resolveInternalAPI(ctx context.Context, cl client.Client, controlNamespace } token = string(sec.Data[naming.ServiceTokenSecretKey]) if token == "" { - return "", "", fmt.Errorf("Secret %s has no %s key", naming.ServiceTokenSecretName, naming.ServiceTokenSecretKey) + return "", "", fmt.Errorf("secret %s has no %s key", naming.ServiceTokenSecretName, naming.ServiceTokenSecretKey) } return fmt.Sprintf("http://%s:%d", ip, platform.APIInternalPort), token, nil diff --git a/cmd/felis/breakglass.go b/cmd/felis/breakglass.go index 0909db3..985eeca 100644 --- a/cmd/felis/breakglass.go +++ b/cmd/felis/breakglass.go @@ -571,12 +571,10 @@ type breakGlassResult struct { backupStatus string // Cloudflare-specific edge detail (set only when connectMethod is Cloudflare) - edgeConfigured bool - edgeAud string - edgeRoutedHosts []string - edgeConfigPath string - edgePanelHostname string - edgeAdminHostname string + edgeConfigured bool + edgeAud string + edgeRoutedHosts []string + edgeConfigPath string } type consoleMode string diff --git a/internal/api/api.go b/internal/api/api.go index 252177b..51f6c1a 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -674,10 +674,9 @@ func principalFromContext(ctx context.Context) *Principal { // #35); cross-replica bounding would need a shared store (out of scope for the // single-replica demo). type cooldownLimiter struct { - mu sync.Mutex - now func() time.Time - last map[string]time.Time - window time.Duration + mu sync.Mutex + now func() time.Time + last map[string]time.Time } // allowed reports whether name may wake now WITHOUT recording the attempt. A diff --git a/internal/api/api_test.go b/internal/api/api_test.go index e5f2b65..888470c 100644 --- a/internal/api/api_test.go +++ b/internal/api/api_test.go @@ -242,7 +242,7 @@ func (f *fakeRepo) QuotaCheck(_ context.Context, userID string, _ string, _ Reso // For hermetic tests, QuotaCheck delegates to the same QuotaAvailable // store — tests that care about per-dimension checks should use // fakeQuotas with direct inspection. - return f.QuotaAvailable(nil, userID) + return f.QuotaAvailable(context.TODO(), userID) } func (f *fakeRepo) UpdateServerResources(_ context.Context, _ string, _, _, _ int) error { return nil } @@ -1335,7 +1335,7 @@ func (c *fakeCluster) GetBySubdomain(_ context.Context, s string) (*ServerInfo, return nil, ErrNotFound } func (c *fakeCluster) ListServers(_ context.Context) ([]ServerInfo, error) { return c.list, nil } -func (c *fakeCluster) Ping(_ context.Context) error { return c.pingErr } +func (c *fakeCluster) Ping(_ context.Context) error { return c.pingErr } func (c *fakeCluster) SetDesiredState(_ context.Context, n string, s v1alpha1.DesiredState) error { c.desired[n] = s return nil diff --git a/internal/api/handlers_user.go b/internal/api/handlers_user.go index 818e58e..c46e732 100644 --- a/internal/api/handlers_user.go +++ b/internal/api/handlers_user.go @@ -705,8 +705,8 @@ func (a *API) handlePatchServer(w http.ResponseWriter, r *http.Request) { // base ceiling to widen (this endpoint does not read the current spec back), so // it is rejected rather than guessed. var ( - newResources corev1.ResourceRequirements - resUpdated bool + newResources corev1.ResourceRequirements + resUpdated bool ) if body.Memory != nil { javaMemory, resources, err := resolveResources(*body.Memory, body.Resources) diff --git a/internal/api/pgrepo.go b/internal/api/pgrepo.go index 2627902..4eb83ad 100644 --- a/internal/api/pgrepo.go +++ b/internal/api/pgrepo.go @@ -1401,7 +1401,7 @@ func (p *PGRepo) UpdateUser(ctx context.Context, userID string, patch UpdateUser argn++ args = append(args, userID) - q := `UPDATE users SET ` + fmt.Sprintf("%s", sets[0]) + q := "UPDATE users SET " + sets[0] for _, s := range sets[1:] { q += ", " + s } diff --git a/internal/api/session.go b/internal/api/session.go index 12883eb..e966725 100644 --- a/internal/api/session.go +++ b/internal/api/session.go @@ -7,7 +7,6 @@ import ( "encoding/base64" "encoding/hex" "encoding/json" - "errors" "fmt" "net" "net/http" @@ -185,7 +184,3 @@ func localAuthEnabled(ctx context.Context, repo Repo) bool { // ensure SessionAuth satisfies ExternalAuth at compile time. var _ ExternalAuth = SessionAuth{} - -// errIsNotFound is a small helper so handlers can branch on the repo's sentinel -// without importing errors at every call site. -func errIsNotFound(err error) bool { return errors.Is(err, ErrNotFound) } diff --git a/internal/backupjob/jobspec_test.go b/internal/backupjob/jobspec_test.go index 92f348d..da220f5 100644 --- a/internal/backupjob/jobspec_test.go +++ b/internal/backupjob/jobspec_test.go @@ -181,8 +181,8 @@ func TestBackupJobArgsCarryServerAndOwner(t *testing.T) { func TestBackupJobRejectsMissingInputs(t *testing.T) { for _, tc := range []struct { - name string - mut func(*JobParams) + name string + mut func(*JobParams) }{ {"no image", func(p *JobParams) { p.Image = "" }}, {"no world pvc", func(p *JobParams) { p.WorldPVC = "" }}, diff --git a/internal/platform/workloads.go b/internal/platform/workloads.go index 3ed4f4f..74c5676 100644 --- a/internal/platform/workloads.go +++ b/internal/platform/workloads.go @@ -47,11 +47,11 @@ const ( // and the service token is a credential, so writing either into a checked-in // manifest is a hard red line. The deployment provisions both Secrets // out-of-band before applying these workloads. - configSecretName = "felis-config" - configSecretKey = "felis.toml" - configMountPath = "/etc/felis" - configFilePath = "/etc/felis/felis.toml" - felisBinaryPath = "/usr/local/bin/felis" + configSecretName = "felis-config" + configSecretKey = "felis.toml" + configMountPath = "/etc/felis" + configFilePath = "/etc/felis/felis.toml" + felisBinaryPath = "/usr/local/bin/felis" // Single-sourced with the operator, which injects the same Secret into the // login system server's pod (see internal/naming). serviceTokenSecretName = naming.ServiceTokenSecretName diff --git a/internal/updater/gatherer_test.go b/internal/updater/gatherer_test.go index 960337e..7a067fc 100644 --- a/internal/updater/gatherer_test.go +++ b/internal/updater/gatherer_test.go @@ -37,10 +37,10 @@ const ( func TestVersionFromCLI(t *testing.T) { cases := []struct { - name string - raw string + name string + raw string wantCore [3]int - wantStr string + wantStr string }{ {"k3s keeps +build stable", k3sVersionBanner, [3]int{1, 36, 2}, "v1.36.2+k3s1"}, {"cloudflared calver", cloudflaredVersionBanner, [3]int{2026, 6, 1}, "2026.6.1"}, diff --git a/internal/updater/github_test.go b/internal/updater/github_test.go index 519480c..b3964c0 100644 --- a/internal/updater/github_test.go +++ b/internal/updater/github_test.go @@ -46,10 +46,10 @@ func ghFixtureServer(body string) *httptest.Server { // while the numeric core is what comparison uses. func TestGitHubLatestStableParsesRealTags(t *testing.T) { cases := []struct { - name string - repo string - body string - wantMajMinPat [3]int + name string + repo string + body string + wantMajMinPat [3]int wantRawInReport string }{ {"cloudflared CalVer", "cloudflare/cloudflared", cloudflaredLatestFixture, [3]int{2026, 6, 1}, "2026.6.1"}, diff --git a/internal/updater/papermc_test.go b/internal/updater/papermc_test.go index fb48961..7d10644 100644 --- a/internal/updater/papermc_test.go +++ b/internal/updater/papermc_test.go @@ -17,6 +17,7 @@ import ( // - the "versions" object groups the ENTIRE 3.x line under a single key "3.0.0" // (not per-minor keys), so a parser that trusted the group key to bound the // versions inside it would be wrong — proof the key-agnostic flatten is required. +// // (The v2 API this replaces now returns HTTP 410.) const velocityV3Fixture = `{ "project": {"id": "velocity", "name": "Velocity"}, diff --git a/internal/updates/plan_test.go b/internal/updates/plan_test.go index 069af32..cfb5264 100644 --- a/internal/updates/plan_test.go +++ b/internal/updates/plan_test.go @@ -51,7 +51,7 @@ func TestWindowContains(t *testing.T) { // The load-bearing invariants live here. Each row is a single component evaluated // against a latest map, at a fixed `now`, asserting the Kind the plan must yield. func TestPlanUpdatesInvariants(t *testing.T) { - now := time.Date(2026, 7, 1, 3, 30, 0, 0, time.UTC) // inside the window below + now := time.Date(2026, 7, 1, 3, 30, 0, 0, time.UTC) // inside the window below openWin := Window{ Start: time.Date(2026, 7, 1, 3, 0, 0, 0, time.UTC), End: time.Date(2026, 7, 1, 4, 0, 0, 0, time.UTC), @@ -62,10 +62,10 @@ func TestPlanUpdatesInvariants(t *testing.T) { } cases := []struct { - name string - comp Component - latest string // "" ⇒ absent from the map (unknown latest) - want ActionKind + name string + comp Component + latest string // "" ⇒ absent from the map (unknown latest) + want ActionKind }{ { name: "pinned is never touched even with a newer stable upstream", @@ -162,11 +162,11 @@ func TestPlanUpdatesPreservesOrderAndPending(t *testing.T) { End: time.Date(2026, 7, 1, 4, 0, 0, 0, time.UTC), } comps := []Component{ - {Name: "felis-api", Current: mustV(t, "1.4.0"), Policy: PolicyScheduled, Manageable: true, Window: win}, // apply - {Name: "k3s", Current: mustV(t, "v1.30.2+k3s1"), Policy: PolicyNotify, Manageable: true}, // notify - {Name: "cloudflared", Current: mustV(t, "2024.2.1"), Policy: PolicyScheduled, Manageable: true}, // no window ⇒ notify - {Name: "velocity", Current: mustV(t, "3.3.0"), Policy: PolicyScheduled, Manageable: false}, // off-cluster ⇒ notify - {Name: "mc-survival", Current: mustV(t, "1.20.1"), Policy: PolicyPinned}, // pinned + {Name: "felis-api", Current: mustV(t, "1.4.0"), Policy: PolicyScheduled, Manageable: true, Window: win}, // apply + {Name: "k3s", Current: mustV(t, "v1.30.2+k3s1"), Policy: PolicyNotify, Manageable: true}, // notify + {Name: "cloudflared", Current: mustV(t, "2024.2.1"), Policy: PolicyScheduled, Manageable: true}, // no window ⇒ notify + {Name: "velocity", Current: mustV(t, "3.3.0"), Policy: PolicyScheduled, Manageable: false}, // off-cluster ⇒ notify + {Name: "mc-survival", Current: mustV(t, "1.20.1"), Policy: PolicyPinned}, // pinned } latest := map[string]Version{ "felis-api": mustV(t, "1.5.0"), diff --git a/internal/updates/version_test.go b/internal/updates/version_test.go index 47b1584..651da62 100644 --- a/internal/updates/version_test.go +++ b/internal/updates/version_test.go @@ -9,18 +9,18 @@ func TestParseTolerant(t *testing.T) { pre string }{ {"1.2.3", 1, 2, 3, ""}, - {"v1.2.3", 1, 2, 3, ""}, // leading v - {"V1.2.3", 1, 2, 3, ""}, // leading V - {"v1.30.2+k3s1", 1, 30, 2, ""}, // k3s build suffix ignored - {"1.30.2+k3s1", 1, 30, 2, ""}, // build suffix, no v - {"2024.2.1", 2024, 2, 1, ""}, // cloudflared calendar version - {"1.2.3-rc.1", 1, 2, 3, "rc.1"}, // prerelease - {"v3.3.0-SNAPSHOT", 3, 3, 0, "SNAPSHOT"}, // velocity-style - {"1.2.3-rc.1+build.9", 1, 2, 3, "rc.1"}, // prerelease AND build - {"v0.0.0+g1a2b3c4", 0, 0, 0, ""}, // stamp of a build pinned to a ref with no tag behind it - {"v2", 2, 0, 0, ""}, // missing minor/patch fill 0 - {"2.0", 2, 0, 0, ""}, // missing patch fills 0 - {" v1.2.3 ", 1, 2, 3, ""}, // surrounding whitespace + {"v1.2.3", 1, 2, 3, ""}, // leading v + {"V1.2.3", 1, 2, 3, ""}, // leading V + {"v1.30.2+k3s1", 1, 30, 2, ""}, // k3s build suffix ignored + {"1.30.2+k3s1", 1, 30, 2, ""}, // build suffix, no v + {"2024.2.1", 2024, 2, 1, ""}, // cloudflared calendar version + {"1.2.3-rc.1", 1, 2, 3, "rc.1"}, // prerelease + {"v3.3.0-SNAPSHOT", 3, 3, 0, "SNAPSHOT"}, // velocity-style + {"1.2.3-rc.1+build.9", 1, 2, 3, "rc.1"}, // prerelease AND build + {"v0.0.0+g1a2b3c4", 0, 0, 0, ""}, // stamp of a build pinned to a ref with no tag behind it + {"v2", 2, 0, 0, ""}, // missing minor/patch fill 0 + {"2.0", 2, 0, 0, ""}, // missing patch fills 0 + {" v1.2.3 ", 1, 2, 3, ""}, // surrounding whitespace } for _, c := range cases { v, err := Parse(c.in)