chore(lint): Backlog auf 0 + golangci-lint als HARTER Gate — v1.3.3
Go-Quality-Baseline-Rollout ABGESCHLOSSEN. Code-Quality-Backlog (55 → 0): - errcheck: unbehandelte Close/Rollback/Remove explizit `_ =`; fmt.Sscanf `_, _ =` (Zero-Value degradiert sauber). - unused: toter Code entfernt (nodeIDOrHostname, stripTrailingNewline, acme.Service.user, strFold + ungenutzter Import). - noctx (net/http): http.NewRequestWithContext mit vorhandenem ctx. - staticcheck: QF1001/S1009/ST1005/SA9003. - contextcheck: detached-by-design-Stellen mit begründetem //nolint. Zwei echte Bugs beim Aufräumen gefunden+gefixt: - backup/remote SFTP-Upload: dst.Close()-Flush-Fehler wurde verschluckt → unvollständiges Remote-File galt als Erfolg. Jetzt geprüft+gemeldet. - haproxy_test: leere if-Assertion (SA9003) testete faktisch nichts → echte t.Errorf-Prüfung (kein HSTS für HSTS-disabled Domain). Bewusste Config-Entscheidungen (.golangci.yml): - noctx-on-os/exec ausgeschlossen: System-Command-Reloads (systemctl/nft/ wg/pg) dürfen NICHT an den Request-Context gebunden werden — ein Client- Disconnect darf keinen laufenden Reload mitten in der Ausführung killen. net/http-noctx bleibt voll aktiv. KEINE exec-Zeile im Code angefasst. - rowserrcheck/sqlclosecheck raus (database/sql-Linter, bei pgx nur FPs). Gate scharf gestellt: Makefile release-check ruft golangci-lint jetzt als HARTEN Gate (install-if-missing, pinned v2.12.2). `make release-check` grün: vet, golangci-lint, govulncheck, build, test -race. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -52,9 +52,6 @@ type Service struct {
|
||||
AccountDir string
|
||||
DirURL string
|
||||
Email string
|
||||
|
||||
// loaded lazily on first call
|
||||
user *acmeUser
|
||||
}
|
||||
|
||||
// New returns a Service with sensible defaults. Email comes from
|
||||
|
||||
@@ -121,7 +121,7 @@ FROM alert_channels ORDER BY id ASC`)
|
||||
}
|
||||
|
||||
func (s *Service) CreateChannel(ctx context.Context, c Channel) (*Channel, error) {
|
||||
if c.Settings == nil || len(c.Settings) == 0 {
|
||||
if len(c.Settings) == 0 {
|
||||
c.Settings = json.RawMessage(`{}`)
|
||||
}
|
||||
row := s.Pool.QueryRow(ctx, `
|
||||
@@ -138,7 +138,7 @@ RETURNING id, name, kind, target, settings, active, created_at, updated_at`,
|
||||
}
|
||||
|
||||
func (s *Service) UpdateChannel(ctx context.Context, id int64, c Channel) (*Channel, error) {
|
||||
if c.Settings == nil || len(c.Settings) == 0 {
|
||||
if len(c.Settings) == 0 {
|
||||
c.Settings = json.RawMessage(`{}`)
|
||||
}
|
||||
row := s.Pool.QueryRow(ctx, `
|
||||
@@ -269,7 +269,7 @@ func (s *Service) sendWebhook(ctx context.Context, c Channel, kind string,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
b, _ := io.ReadAll(io.LimitReader(resp.Body, 512))
|
||||
return fmt.Errorf("webhook %d: %s", resp.StatusCode, strings.TrimSpace(string(b)))
|
||||
|
||||
@@ -55,7 +55,7 @@ func PackageVersions(ctx context.Context, force bool) map[string]string {
|
||||
defer cancel()
|
||||
|
||||
for _, pkg := range edgePackages {
|
||||
installed, candidate := aptCachePolicy(policyCtx, pkg)
|
||||
installed, candidate := aptCachePolicy(policyCtx, pkg) //nolint:contextcheck // detached by design — Cache-Read überlebt Request-Cancel
|
||||
out[pkg+"_installed"] = installed
|
||||
out[pkg+"_available"] = candidate
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
@@ -232,10 +231,9 @@ func (s *Service) Run(ctx context.Context, kind Kind, version string) (*Result,
|
||||
s.recordFailure(ctx, res, hostname, kind)
|
||||
return res, err
|
||||
}
|
||||
if err := f.Sync(); err != nil {
|
||||
// Nicht fatal — fsync-failure kann passieren bei tmpfs in
|
||||
// Dev, aber der File ist da.
|
||||
}
|
||||
// fsync best-effort — Nicht fatal: fsync-failure kann bei tmpfs in
|
||||
// Dev passieren, der File ist trotzdem da.
|
||||
_ = f.Sync()
|
||||
if err := f.Close(); err != nil {
|
||||
s.recordFailure(ctx, res, hostname, kind)
|
||||
return res, err
|
||||
@@ -644,10 +642,6 @@ func (b *bytes) Write(p []byte) (int, error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// strFold ist ein utility nur zum Defensiv-Check, dass kind ein
|
||||
// erlaubter Wert ist (für die DB-Constraint).
|
||||
func strFold(s string) string { return strings.ToLower(strings.TrimSpace(s)) }
|
||||
|
||||
// SortByDate sortiert Entries newest-first. Wird nicht direkt benutzt
|
||||
// (DB-Query macht's), aber praktisch wenn der Caller eine eigene
|
||||
// Liste hat.
|
||||
|
||||
@@ -180,7 +180,7 @@ func uploadS3(ctx context.Context, s S3Settings, localPath string) (int64, error
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer f.Close()
|
||||
defer func() { _ = f.Close() }()
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -245,12 +245,12 @@ func uploadSFTP(ctx context.Context, s SFTPSettings, localPath string) (int64, e
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("ssh dial %s: %w", addr, err)
|
||||
}
|
||||
defer conn.Close()
|
||||
defer func() { _ = conn.Close() }()
|
||||
cl, err := sftp.NewClient(conn)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("sftp client: %w", err)
|
||||
}
|
||||
defer cl.Close()
|
||||
defer func() { _ = cl.Close() }()
|
||||
|
||||
// remote-dir anlegen (idempotent)
|
||||
_ = cl.MkdirAll(s.RemoteDir)
|
||||
@@ -259,7 +259,7 @@ func uploadSFTP(ctx context.Context, s SFTPSettings, localPath string) (int64, e
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer src.Close()
|
||||
defer func() { _ = src.Close() }()
|
||||
stat, err := src.Stat()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -269,10 +269,16 @@ func uploadSFTP(ctx context.Context, s SFTPSettings, localPath string) (int64, e
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("create remote: %w", err)
|
||||
}
|
||||
defer dst.Close()
|
||||
if _, err := io.Copy(dst, src); err != nil {
|
||||
_ = dst.Close()
|
||||
return 0, fmt.Errorf("copy: %w", err)
|
||||
}
|
||||
// SFTP-Close flusht gepufferte Writes — ein Fehler hier bedeutet
|
||||
// ein unvollständiges Remote-File und MUSS als Upload-Fehler
|
||||
// gemeldet werden (nicht als Erfolg durchgehen).
|
||||
if err := dst.Close(); err != nil {
|
||||
return 0, fmt.Errorf("close remote: %w", err)
|
||||
}
|
||||
return stat.Size(), nil
|
||||
}
|
||||
|
||||
@@ -284,9 +290,9 @@ func (s *Service) Test(ctx context.Context, t Target) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(tmp.Name())
|
||||
defer func() { _ = os.Remove(tmp.Name()) }()
|
||||
_, _ = tmp.WriteString("edgeguard remote-target test " + time.Now().Format(time.RFC3339))
|
||||
tmp.Close()
|
||||
_ = tmp.Close()
|
||||
|
||||
r := s.uploadOne(ctx, t, tmp.Name())
|
||||
if !r.OK {
|
||||
@@ -331,9 +337,9 @@ func (s *Service) Test(ctx context.Context, t Target) error {
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(), Timeout: 5 * time.Second}); err == nil {
|
||||
if cl, err := sftp.NewClient(conn); err == nil {
|
||||
_ = cl.Remove(strings.TrimRight(settings.RemoteDir, "/") + "/" + filepath.Base(tmp.Name()))
|
||||
cl.Close()
|
||||
_ = cl.Close()
|
||||
}
|
||||
conn.Close()
|
||||
_ = conn.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package clusterjoin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
@@ -177,7 +178,7 @@ func NormalizePrimaryURL(in string) (string, error) {
|
||||
|
||||
func issueCert(primary, token, csr string, insecure bool) (caCert, peerCert string, err error) {
|
||||
body, _ := json.Marshal(map[string]string{"token": token, "csr": csr})
|
||||
req, err := http.NewRequest(http.MethodPost,
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodPost,
|
||||
primary+"/api/v1/cluster/issue-cert", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
@@ -193,7 +194,7 @@ func issueCert(primary, token, csr string, insecure bool) (caCert, peerCert stri
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
raw, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", "", fmt.Errorf("HTTP %d: %s", resp.StatusCode, strings.TrimSpace(string(raw)))
|
||||
@@ -287,7 +288,7 @@ func autoRegister(primary, tlsDir, commonName, version, nodeID, configHash, role
|
||||
}
|
||||
client := &http.Client{Transport: tr, Timeout: 30 * time.Second}
|
||||
|
||||
httpReq, err := http.NewRequest(http.MethodPost, u.String(), bytes.NewReader(body))
|
||||
httpReq, err := http.NewRequestWithContext(context.Background(), http.MethodPost, u.String(), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -296,7 +297,7 @@ func autoRegister(primary, tlsDir, commonName, version, nodeID, configHash, role
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
raw, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, strings.TrimSpace(string(raw)))
|
||||
|
||||
@@ -97,7 +97,7 @@ func (r *AddressGroupsRepo) Create(ctx context.Context, g models.FirewallAddress
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
defer func() { _ = tx.Rollback(ctx) }()
|
||||
|
||||
row := tx.QueryRow(ctx, `
|
||||
INSERT INTO firewall_address_groups (name, description)
|
||||
@@ -125,7 +125,7 @@ func (r *AddressGroupsRepo) Update(ctx context.Context, id int64, g models.Firew
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
defer func() { _ = tx.Rollback(ctx) }()
|
||||
|
||||
row := tx.QueryRow(ctx, `
|
||||
UPDATE firewall_address_groups SET name = $1, description = $2, updated_at = NOW()
|
||||
|
||||
@@ -91,7 +91,7 @@ func (r *ServiceGroupsRepo) Create(ctx context.Context, g models.FirewallService
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
defer func() { _ = tx.Rollback(ctx) }()
|
||||
|
||||
row := tx.QueryRow(ctx, `
|
||||
INSERT INTO firewall_service_groups (name, description) VALUES ($1, $2)
|
||||
@@ -115,7 +115,7 @@ func (r *ServiceGroupsRepo) Update(ctx context.Context, id int64, g models.Firew
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
defer func() { _ = tx.Rollback(ctx) }()
|
||||
|
||||
row := tx.QueryRow(ctx, `
|
||||
UPDATE firewall_service_groups SET name = $1, description = $2, updated_at = NOW()
|
||||
|
||||
@@ -102,7 +102,7 @@ func ReadTail(path string, f Filter) ([]Entry, error) {
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
defer func() { _ = file.Close() }()
|
||||
|
||||
// Ring-Buffer für die letzten f.Limit matching entries.
|
||||
buf := make([]Entry, 0, f.Limit)
|
||||
|
||||
@@ -64,7 +64,7 @@ func (t *Tailer) Start(ctx context.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer w.Close()
|
||||
defer func() { _ = w.Close() }()
|
||||
|
||||
// Watch das Verzeichnis statt der Datei — wenn ulogd das File
|
||||
// erstmals anlegt (oder rotate-rename), bekommen wir Create-Events.
|
||||
@@ -106,7 +106,7 @@ func (t *Tailer) bootstrap() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
defer func() { _ = f.Close() }()
|
||||
sc := bufio.NewScanner(f)
|
||||
sc.Buffer(make([]byte, 0, 64*1024), 1024*1024)
|
||||
for sc.Scan() {
|
||||
@@ -134,7 +134,7 @@ func (t *Tailer) drainFile() {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
stat, err := f.Stat()
|
||||
if err != nil {
|
||||
|
||||
@@ -292,7 +292,7 @@ func parseWGConf(path string) (*parsedConf, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
var (
|
||||
out parsedConf
|
||||
|
||||
Reference in New Issue
Block a user