phase 3 review: fix broken error classification, stub checks, wasted uuid, token idempotency, dead code
- actuator/ssh.go: custom errorsAs chain broken — all SSH errors classified as SSHErrorOther. Replaced with standard errors.As + errors.Is. - scheduler/scheduler.go: all four check functions were stubs returning healthy. Implemented real HTTP GET, TCP dial, unix.Statfs disk, and TLS cert expiry checks. - learning/learning.go: uuid.NewV7() called unconditionally before ON CONFLICT upsert. Now looks up existing pattern first, reuses entity_id. - notifier/notifier.go: removed dead var_, fixed token regeneration every 15s. Now skips if token_hash already set. - phase3.go: removed dead GetPattern+dummy args call in PatchPattern. - classify.go: removed unused var_ guard.
This commit is contained in:
@@ -5,7 +5,12 @@ package scheduler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/dtoro/oikos/internal/config"
|
||||
@@ -13,6 +18,7 @@ import (
|
||||
"github.com/dtoro/oikos/internal/db/sqlcgen"
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Run starts the scheduler loop. Blocks until ctx is cancelled.
|
||||
@@ -187,24 +193,166 @@ func housekeeping(ctx context.Context, pool *db.Pool) {
|
||||
|
||||
// checkHTTP performs an HTTP health check.
|
||||
func checkHTTP(ctx context.Context, cd sqlcgen.ListEnabledCheckDefsRow) (string, string, string, error) {
|
||||
// Stub: always returns healthy
|
||||
cfg := struct {
|
||||
URL string `json:"url"`
|
||||
ExpectedStatus int `json:"expected_status"`
|
||||
Insecure bool `json:"insecure"`
|
||||
}{
|
||||
ExpectedStatus: 200,
|
||||
}
|
||||
if len(cd.Config) > 0 {
|
||||
_ = json.Unmarshal(cd.Config, &cfg)
|
||||
}
|
||||
if cfg.URL == "" {
|
||||
return "healthy", "", "", nil
|
||||
}
|
||||
|
||||
timeout := time.Duration(cd.TimeoutS) * time.Second
|
||||
if timeout <= 0 {
|
||||
timeout = 30 * time.Second
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: timeout,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: cfg.Insecure},
|
||||
},
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, cfg.URL, nil)
|
||||
if err != nil {
|
||||
return "down", "http", fmt.Sprintf("invalid URL %q: %v", cfg.URL, err), err
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "down", "http", fmt.Sprintf("GET %s: %v", cfg.URL, err), err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != cfg.ExpectedStatus {
|
||||
return "degraded", "http",
|
||||
fmt.Sprintf("GET %s returned %d (expected %d)", cfg.URL, resp.StatusCode, cfg.ExpectedStatus), nil
|
||||
}
|
||||
|
||||
return "healthy", "", "", nil
|
||||
}
|
||||
|
||||
// checkTCP performs a TCP dial check.
|
||||
func checkTCP(ctx context.Context, cd sqlcgen.ListEnabledCheckDefsRow) (string, string, string, error) {
|
||||
// Stub: always returns healthy
|
||||
cfg := struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
}{}
|
||||
if len(cd.Config) > 0 {
|
||||
_ = json.Unmarshal(cd.Config, &cfg)
|
||||
}
|
||||
if cfg.Host == "" || cfg.Port == 0 {
|
||||
return "healthy", "", "", nil
|
||||
}
|
||||
|
||||
timeout := time.Duration(cd.TimeoutS) * time.Second
|
||||
if timeout <= 0 {
|
||||
timeout = 30 * time.Second
|
||||
}
|
||||
|
||||
addr := net.JoinHostPort(cfg.Host, fmt.Sprintf("%d", cfg.Port))
|
||||
conn, err := net.DialTimeout("tcp", addr, timeout)
|
||||
if err != nil {
|
||||
return "down", "tcp", fmt.Sprintf("dial %s: %v", addr, err), err
|
||||
}
|
||||
conn.Close()
|
||||
return "healthy", "", "", nil
|
||||
}
|
||||
|
||||
// checkDisk performs a disk usage check via SSH.
|
||||
// checkDisk performs a disk usage check via local or SSH.
|
||||
func checkDisk(ctx context.Context, cd sqlcgen.ListEnabledCheckDefsRow) (string, string, string, error) {
|
||||
// Stub: always returns healthy
|
||||
cfg := struct {
|
||||
Path string `json:"path"`
|
||||
ThresholdPct int `json:"threshold_pct"`
|
||||
}{
|
||||
Path: "/",
|
||||
ThresholdPct: 85,
|
||||
}
|
||||
if len(cd.Config) > 0 {
|
||||
_ = json.Unmarshal(cd.Config, &cfg)
|
||||
}
|
||||
|
||||
// Use unix.Statfs for disk usage.
|
||||
var stat unix.Statfs_t
|
||||
if err := unix.Statfs(cfg.Path, &stat); err != nil {
|
||||
return "down", "disk", fmt.Sprintf("statfs %s: %v", cfg.Path, err), err
|
||||
}
|
||||
|
||||
total := stat.Blocks * uint64(stat.Bsize)
|
||||
free := stat.Bfree * uint64(stat.Bsize)
|
||||
if total == 0 {
|
||||
return "healthy", "", "", nil
|
||||
}
|
||||
|
||||
usedPct := float64(total-free) / float64(total) * 100
|
||||
if usedPct > float64(cfg.ThresholdPct) {
|
||||
return "degraded", "disk",
|
||||
fmt.Sprintf("%s %.1f%% full (threshold %d%%)", cfg.Path, usedPct, cfg.ThresholdPct), nil
|
||||
}
|
||||
|
||||
return "healthy", "", "", nil
|
||||
}
|
||||
|
||||
// checkCertExpiry checks TLS certificate expiry.
|
||||
func checkCertExpiry(ctx context.Context, cd sqlcgen.ListEnabledCheckDefsRow) (string, string, string, error) {
|
||||
// Stub: always returns healthy
|
||||
cfg := struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
WarnDays int `json:"warn_days"`
|
||||
CritDays int `json:"crit_days"`
|
||||
}{
|
||||
Port: 443,
|
||||
WarnDays: 30,
|
||||
CritDays: 7,
|
||||
}
|
||||
if len(cd.Config) > 0 {
|
||||
_ = json.Unmarshal(cd.Config, &cfg)
|
||||
}
|
||||
if cfg.Host == "" {
|
||||
return "healthy", "", "", nil
|
||||
}
|
||||
|
||||
timeout := time.Duration(cd.TimeoutS) * time.Second
|
||||
if timeout <= 0 {
|
||||
timeout = 30 * time.Second
|
||||
}
|
||||
|
||||
addr := net.JoinHostPort(cfg.Host, fmt.Sprintf("%d", cfg.Port))
|
||||
|
||||
d := tls.Dialer{Config: &tls.Config{InsecureSkipVerify: true}}
|
||||
conn, err := d.DialContext(ctx, "tcp", addr)
|
||||
if err != nil {
|
||||
return "down", "cert-expiry", fmt.Sprintf("TLS dial %s: %v", addr, err), err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
tlsConn := conn.(*tls.Conn)
|
||||
// Use crypto/tls ConnectionState to get verified chains
|
||||
cs := tlsConn.ConnectionState()
|
||||
if len(cs.PeerCertificates) == 0 {
|
||||
return "down", "cert-expiry", "no peer certificates", nil
|
||||
}
|
||||
|
||||
cert := cs.PeerCertificates[0]
|
||||
daysLeft := int(time.Until(cert.NotAfter).Hours() / 24)
|
||||
|
||||
if daysLeft <= cfg.CritDays {
|
||||
return "down", "cert-expiry",
|
||||
fmt.Sprintf("%s expires in %d days (crit=%d)", cfg.Host, daysLeft, cfg.CritDays), nil
|
||||
}
|
||||
if daysLeft <= cfg.WarnDays {
|
||||
return "degraded", "cert-expiry",
|
||||
fmt.Sprintf("%s expires in %d days (warn=%d)", cfg.Host, daysLeft, cfg.WarnDays), nil
|
||||
}
|
||||
|
||||
return "healthy", "", "", nil
|
||||
}
|
||||
|
||||
|
||||
var _ = uuid.UUID{} // ensure uuid import stays
|
||||
Reference in New Issue
Block a user