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
26 changes: 16 additions & 10 deletions docs/onebox.run-v1.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -979,7 +979,8 @@
"type": "string"
},
"interval": {
"description": "Delay between container health probes. Expects a duration such as 30s, 5m, 1h30m or 14d.",
"default": "5s",
"description": "Delay between container health probes, at most 7d. Always written into the generated healthcheck, so the rollout's drain budget is computed from the value the container actually runs with. Expects a duration such as 30s, 5m, 1h30m or 14d.",
"examples": [
"2s"
],
Expand All @@ -996,14 +997,16 @@
"type": "integer"
},
"retries": {
"description": "Consecutive failed probes before the container is unhealthy.",
"default": 3,
"description": "Consecutive failed probes before the container is unhealthy. A draining container leaves rotation after this many probes, so it sets how long a rolling deploy waits for each replica.",
"examples": [
3
],
"type": "integer"
},
"start_period": {
"description": "Startup grace period before failed probes count. Expects a duration such as 30s, 5m, 1h30m or 14d.",
"default": "30s",
"description": "Startup grace period before failed probes count, at most 7d. Always written into the generated healthcheck, so writing down a fast probe interval does not call a booting container unhealthy. Expects a duration such as 30s, 5m, 1h30m or 14d.",
"examples": [
"5s"
],
Expand All @@ -1016,7 +1019,7 @@
"type": "boolean"
},
"within": {
"description": "Maximum time a rollout waits for readiness. Expects a duration such as 30s, 5m, 1h30m or 14d.",
"description": "Maximum time a rollout waits for readiness, at most 7d. Expects a duration such as 30s, 5m, 1h30m or 14d.",
"examples": [
"120s"
],
Expand Down Expand Up @@ -1957,7 +1960,7 @@
},
"properties": {
"grace": {
"description": "Maximum graceful-shutdown time before forced termination. Expects a duration such as 30s, 5m, 1h30m or 14d.",
"description": "Maximum graceful-shutdown time before forced termination, at most 7d. Expects a duration such as 30s, 5m, 1h30m or 14d.",
"examples": [
"30s"
],
Expand All @@ -1971,7 +1974,7 @@
"type": "string"
},
"wait": {
"description": "Time allowed for the proxy to stop routing before shutdown begins. Expects a duration such as 30s, 5m, 1h30m or 14d.",
"description": "Time allowed for the proxy to stop routing before shutdown begins, at most 7d. Expects a duration such as 30s, 5m, 1h30m or 14d.",
"examples": [
"10s"
],
Expand Down Expand Up @@ -2080,7 +2083,8 @@
"type": "string"
},
"interval": {
"description": "Delay between container health probes. Expects a duration such as 30s, 5m, 1h30m or 14d.",
"default": "5s",
"description": "Delay between container health probes, at most 7d. Always written into the generated healthcheck, so the rollout's drain budget is computed from the value the container actually runs with. Expects a duration such as 30s, 5m, 1h30m or 14d.",
"examples": [
"2s"
],
Expand All @@ -2097,14 +2101,16 @@
"type": "integer"
},
"retries": {
"description": "Consecutive failed probes before the container is unhealthy.",
"default": 3,
"description": "Consecutive failed probes before the container is unhealthy. A draining container leaves rotation after this many probes, so it sets how long a rolling deploy waits for each replica.",
"examples": [
3
],
"type": "integer"
},
"start_period": {
"description": "Startup grace period before failed probes count. Expects a duration such as 30s, 5m, 1h30m or 14d.",
"default": "30s",
"description": "Startup grace period before failed probes count, at most 7d. Always written into the generated healthcheck, so writing down a fast probe interval does not call a booting container unhealthy. Expects a duration such as 30s, 5m, 1h30m or 14d.",
"examples": [
"5s"
],
Expand All @@ -2117,7 +2123,7 @@
"type": "boolean"
},
"within": {
"description": "Maximum time a rollout waits for readiness. Expects a duration such as 30s, 5m, 1h30m or 14d.",
"description": "Maximum time a rollout waits for readiness, at most 7d. Expects a duration such as 30s, 5m, 1h30m or 14d.",
"examples": [
"120s"
],
Expand Down
54 changes: 41 additions & 13 deletions internal/app/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/hex"
"fmt"
"strings"
"time"

"gopkg.in/yaml.v3"
)
Expand Down Expand Up @@ -396,9 +397,7 @@ func (p *Spec) renderWorkload(n Names, name string, w Workload, releaseID string
svc["logging"] = lg
}
}
if w.Drain != nil && w.Drain.Grace != "" {
svc["stop_grace_period"] = w.Drain.Grace
}
applyStopGrace(svc, w)
if w.Resources != nil {
if w.Resources.Memory != "" {
svc["mem_limit"] = w.Resources.Memory
Expand Down Expand Up @@ -603,17 +602,24 @@ func healthcheck(h *Health) map[string]any {
default:
return nil
}
out := map[string]any{"test": test}
if h.Interval != "" {
out["interval"] = h.Interval
}
if h.StartPeriod != "" {
out["start_period"] = h.StartPeriod
}
if h.Retries > 0 {
out["retries"] = h.Retries
// Interval, retries and the start period are always written, never left to
// the runtime's default. The rollout budgets the drain against retries × interval, and a
// budget computed from a number the container was not created with is a
// budget that expires while the container is still healthy — which stops it
// while the proxy may still be routing to it. Emitting them is what makes
// the model and the runtime the same.
//
// Every duration goes through the model rather than being echoed as
// authored: `14d` is a duration Onebox accepts and Compose cannot parse, so
// echoing it validates cleanly and then fails the deploy at the compose
// step with `time: unknown unit "d"`.
workload := Workload{Health: h}
return map[string]any{
"test": test,
"interval": composeDuration(h.Interval, workload.HealthInterval()),
"retries": workload.HealthRetries(),
"start_period": composeDuration(h.StartPeriod, workload.HealthStartPeriod()),
}
return out
}

func composeCondition(c string) string {
Expand Down Expand Up @@ -959,3 +965,25 @@ func composePullPolicy(declared string) string {
}
return ""
}

// applyStopGrace writes the authored stop grace, normalised. Compose parses
// durations with its own grammar, which has no day unit: echoing `1d` as
// authored validates here and then fails the deploy with `time: unknown unit
// "d"` — for a value Onebox told the author was fine.
func applyStopGrace(svc map[string]any, w Workload) {
if w.Drain == nil || w.Drain.Grace == "" {
return
}
svc["stop_grace_period"] = composeDuration(w.Drain.Grace, 0)
}

// composeDuration renders a duration in units Compose can parse. The authored
// value wins whenever it parses — including an explicit zero, which is a real
// instruction to the runtime and not the absence of one — and the fallback is
// used only when nothing was written.
func composeDuration(authored string, fallback time.Duration) string {
if d, ok := ParseDuration(authored); ok {
return d.String()
}
return fallback.String()
}
Loading