e3d140c57a
Add a deploy_strategy field to each source's config blob — "" (default), "recreate", or "blue-green" — validated in each source's Validate and read on the deploy path. No new DB column, no migration: the field rides inside the existing SourceConfig JSON and every existing workload decodes "" to its historical behavior (image -> blue-green, others -> recreate). The real gap this closes: dockerfile and static stopped the old container before creating the new one on every redeploy — a downtime window image never had. Their blue-green branch now: - names the new "green" container with a unique suffix so it coexists with the still-serving blue (plumbed into both the container name AND the proxy forwardHost); - skips the collision teardown that destroyed blue early; - gates green — an HTTP readiness probe (deps.Health.Check) when a healthcheck is configured, else the existing liveness window; - swaps the route via a pure upsert (no pre-DeleteRoute) so NPM repoints in place with no gap; - persists green into the single runtime-state row BEFORE reaping blue, so a crash mid-swap can never orphan green or leave the row pointing at a removed container (state.go/teardown.go/reconcile.go stay untouched). image honors explicit "recreate" (reap existing containers after pull, before cutover); its default blue-green path is unchanged. compose stays stack-managed and rejects "blue-green" at Validate so the contract is honest. static forces recreate for storage-backed deno sites — blue-green would mount the same RW volume into both containers at once. Shared helper internal/workload/plugin/strategy.go (ValidateStrategy + BuildGreenName). Backend-only (phase 1); the field is usable today via the app's advanced-JSON editor — a friendly toggle + i18n follow in phase 2. Tests: ValidateStrategy matrix, per-source Validate (incl. the empty-key backward-compat lock), and effectiveStrategy defaults + the deno gate. Design + adversarial review: docs/plans/DEPLOY_STRATEGY_PLAN.md.
49 lines
1.5 KiB
Go
49 lines
1.5 KiB
Go
package plugin
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestValidateStrategy(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
value string
|
|
allowBlueGreen bool
|
|
wantErr bool
|
|
}{
|
|
{"empty always ok (backward compat)", "", true, false},
|
|
{"empty ok when blue-green disallowed", "", false, false},
|
|
{"recreate ok", StrategyRecreate, true, false},
|
|
{"recreate ok when blue-green disallowed", StrategyRecreate, false, false},
|
|
{"blue-green ok when allowed", StrategyBlueGreen, true, false},
|
|
{"blue-green rejected when disallowed (compose)", StrategyBlueGreen, false, true},
|
|
{"reserved rolling rejected (allowed)", "rolling", true, true},
|
|
{"reserved rolling rejected (disallowed)", "rolling", false, true},
|
|
{"junk rejected", "banana", true, true},
|
|
}
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
err := ValidateStrategy(c.value, c.allowBlueGreen)
|
|
if (err != nil) != c.wantErr {
|
|
t.Fatalf("ValidateStrategy(%q, %v) err=%v, wantErr=%v", c.value, c.allowBlueGreen, err, c.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuildGreenName_UniqueSuffixAndDistinct(t *testing.T) {
|
|
base := "tf-build-app-1234abcd"
|
|
a := BuildGreenName(base, time.Unix(1000, 0))
|
|
b := BuildGreenName(base, time.Unix(2000, 0))
|
|
if a == base || b == base {
|
|
t.Fatal("green name must differ from the deterministic base")
|
|
}
|
|
if a == b {
|
|
t.Fatal("different timestamps must yield different green names")
|
|
}
|
|
if len(a) <= len(base) {
|
|
t.Fatalf("green name %q should extend the base %q", a, base)
|
|
}
|
|
}
|