Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"fmt"
"log/slog"
"os"
"strconv"
"strings"
)

Expand Down Expand Up @@ -178,6 +179,15 @@
FlowSyntheticTier string // FLOW_SYNTHETIC_TIER — seeded tier (default free)
FlowSyntheticDisabled string // FLOW_SYNTHETIC_DISABLED — comma list of per-flow kill switches
FlowSyntheticJWTSecret string // JWT_SECRET — shared with api; mints the synthetic session JWT

// Scale-to-zero idle-scaler (deploy_idle_scaler.go, Task #54). INERT unless
// DeployScaleToZeroEnabled is true — the master flag (shared name with the
// api's wake-path flag). When off, the idle-scaler sweep is a no-op (no k8s
// patch, no DB write). DeployScaleToZeroIdleMinutes is the no-activity
// threshold before an app is descheduled (default 30; floored at 5 to avoid
// pathological flapping). Enabling is an operator action after a canary.
DeployScaleToZeroEnabled bool // DEPLOY_SCALE_TO_ZERO_ENABLED — master flag (default false)
DeployScaleToZeroIdleMinutes int // DEPLOY_SCALE_TO_ZERO_IDLE_MINUTES — idle threshold (default 30)
}

// ErrMissingConfig is returned when a required env var is absent.
Expand Down Expand Up @@ -294,6 +304,20 @@
FlowSyntheticTier: os.Getenv("FLOW_SYNTHETIC_TIER"),
FlowSyntheticDisabled: os.Getenv("FLOW_SYNTHETIC_DISABLED"),
FlowSyntheticJWTSecret: os.Getenv("JWT_SECRET"),

// Scale-to-zero idle-scaler (Task #54). Default OFF; idle threshold
// default 30 min (parsed below).
DeployScaleToZeroEnabled: os.Getenv("DEPLOY_SCALE_TO_ZERO_ENABLED") == "true",
}

// DEPLOY_SCALE_TO_ZERO_IDLE_MINUTES: minutes of no-activity before an app is
// descheduled. Default 30; an unset / unparseable / sub-5 value floors to 30

Check warning on line 314 in internal/config/config.go

View workflow job for this annotation

GitHub Actions / typos

"unparseable" should be "unparsable".
// so a misconfig can't make the scaler aggressively flap apps to sleep.
cfg.DeployScaleToZeroIdleMinutes = 30
if v := strings.TrimSpace(os.Getenv("DEPLOY_SCALE_TO_ZERO_IDLE_MINUTES")); v != "" {
if n, err := strconv.Atoi(v); err == nil && n >= 5 {
cfg.DeployScaleToZeroIdleMinutes = n
}
}

// Fall back to the shared object-store bucket when the operator hasn't
Expand Down
30 changes: 30 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,36 @@ func TestLoad_Defaults(t *testing.T) {
}
}

// TestLoad_DeployScaleToZeroIdleMinutes exercises the env-parse branch for
// DEPLOY_SCALE_TO_ZERO_IDLE_MINUTES: a valid value is honoured; an invalid /
// sub-5 value floors to the 30-minute default.
func TestLoad_DeployScaleToZeroIdleMinutes(t *testing.T) {
t.Run("valid override", func(t *testing.T) {
clearEnv(t)
t.Setenv("DATABASE_URL", "postgres://localhost/db")
t.Setenv("DEPLOY_SCALE_TO_ZERO_IDLE_MINUTES", "45")
if got := Load().DeployScaleToZeroIdleMinutes; got != 45 {
t.Errorf("DeployScaleToZeroIdleMinutes = %d; want 45", got)
}
})
t.Run("sub-5 floors to default", func(t *testing.T) {
clearEnv(t)
t.Setenv("DATABASE_URL", "postgres://localhost/db")
t.Setenv("DEPLOY_SCALE_TO_ZERO_IDLE_MINUTES", "3")
if got := Load().DeployScaleToZeroIdleMinutes; got != 30 {
t.Errorf("sub-5 DeployScaleToZeroIdleMinutes = %d; want floor 30", got)
}
})
t.Run("non-numeric floors to default", func(t *testing.T) {
clearEnv(t)
t.Setenv("DATABASE_URL", "postgres://localhost/db")
t.Setenv("DEPLOY_SCALE_TO_ZERO_IDLE_MINUTES", "abc")
if got := Load().DeployScaleToZeroIdleMinutes; got != 30 {
t.Errorf("non-numeric DeployScaleToZeroIdleMinutes = %d; want floor 30", got)
}
})
}

func TestLoad_PanicsWithoutDatabaseURL(t *testing.T) {
clearEnv(t)
defer func() {
Expand Down
Loading
Loading