diff --git a/cmd/settings-catalog/main.go b/cmd/settings-catalog/main.go index d64a077bb..55a0a539f 100644 --- a/cmd/settings-catalog/main.go +++ b/cmd/settings-catalog/main.go @@ -320,6 +320,8 @@ func knownFunction(name string) (functionSpec, bool) { "GetBoolOrDefault": directSpec("bool", 3), "GetBoolOrFalse": directSpec("bool", 2), "GetBoolOrTrue": directSpec("bool", 2), + "GetDuration": directSpec("string", 2), + "GetDurationOrDefault": directSpec("string", 3), "GetMap": directSpec("map[string]string", 2), "GetMapOrEmpty": directSpec("map[string]string", 2), "GetEnvVars": { diff --git a/docs/09-Configuration reference/01-Settings.md b/docs/09-Configuration reference/01-Settings.md index a3582c03e..9b7983751 100644 --- a/docs/09-Configuration reference/01-Settings.md +++ b/docs/09-Configuration reference/01-Settings.md @@ -68,6 +68,8 @@ While we have some basic types (string, number, bool ...), we also have some com | gateway.ingress.tls.enabled | bool | true | Enable TLS if not enabled at Gateway CRD level | | gateway.caddyfile.trusted-proxies | string | 10.0.0.0/8,192.168.0.0/16 | Comma-separated list of IP ranges (CIDRs) of trusted proxy servers. Caddy will parse the real client IP from HTTP headers when requests come from these proxies. Use `private_ranges` to match all private IPv4 and IPv6 ranges. | | gateway.caddyfile.trusted-proxies-strict | bool | false | Enable strict (right-to-left) parsing of the X-Forwarded-For header. Recommended when using upstream proxies like HAProxy, Cloudflare, AWS ALB, or CloudFront. | +| gateway.caddyfile.shutdown-delay | string | | Delay before Caddy starts the graceful shutdown sequence, allowing load balancers to remove the pod from rotation before in-flight requests are drained. Use Go duration format (e.g., 30s, 5m). | +| gateway.caddyfile.grace-period | string | | Maximum time to wait for in-flight requests to complete before forcefully closing connections during shutdown. Use Go duration format (e.g., 30s, 5m). | | gateway.config.idle-timeout | string | 10m | Configure the idle timeout for client connections (default: 5m). Use Go duration format (e.g., 30s, 5m, 1h). | | gateway.dns.private.enabled | bool | false | Enable generation of private DNS endpoints for the gateway | | gateway.dns.private.dns-names | string | | DNS name pattern(s) for private DNS endpoints. Comma-separated list. Supports `{stack}` placeholder | diff --git a/docs/09-Configuration reference/settings.catalog.json b/docs/09-Configuration reference/settings.catalog.json index 41018a909..a4316ca86 100644 --- a/docs/09-Configuration reference/settings.catalog.json +++ b/docs/09-Configuration reference/settings.catalog.json @@ -191,6 +191,20 @@ "internal/resources/applications/application.go:357" ] }, + { + "key": "gateway.caddyfile.grace-period", + "valueType": "string", + "sources": [ + "internal/resources/gateways/configuration.go:41" + ] + }, + { + "key": "gateway.caddyfile.shutdown-delay", + "valueType": "string", + "sources": [ + "internal/resources/gateways/configuration.go:33" + ] + }, { "key": "gateway.caddyfile.trusted-proxies", "valueType": "string[]", @@ -209,7 +223,7 @@ "key": "gateway.config.idle-timeout", "valueType": "string", "sources": [ - "internal/resources/gateways/configuration.go:33" + "internal/resources/gateways/configuration.go:49" ] }, { diff --git a/internal/resources/gateways/Caddyfile.gotpl b/internal/resources/gateways/Caddyfile.gotpl index 337fed3c4..32913fc25 100644 --- a/internal/resources/gateways/Caddyfile.gotpl +++ b/internal/resources/gateways/Caddyfile.gotpl @@ -50,6 +50,13 @@ # Global metrics endpoint (moved from servers block - deprecated location) metrics + {{- if .ShutdownDelay }} + shutdown_delay {{ .ShutdownDelay }} + {{- end }} + {{- if .GracePeriod }} + grace_period {{ .GracePeriod }} + {{- end }} + servers { {{- if and .TrustedProxies (gt (len .TrustedProxies) 0) }} trusted_proxies {{ .TrustedProxies }} diff --git a/internal/resources/gateways/caddyfile.go b/internal/resources/gateways/caddyfile.go index 364e40df3..629abb747 100644 --- a/internal/resources/gateways/caddyfile.go +++ b/internal/resources/gateways/caddyfile.go @@ -2,6 +2,7 @@ package gateways import ( "strings" + "time" collectionutils "github.com/formancehq/go-libs/v5/pkg/types/collections" @@ -55,9 +56,23 @@ func withTrustedProxiesStrict() func(data map[string]any) error { } } -func withIdleTimeout(timeout string) func(data map[string]any) error { +func withIdleTimeout(timeout time.Duration) func(data map[string]any) error { return func(data map[string]any) error { - data["IdleTimeout"] = timeout + data["IdleTimeout"] = timeout.String() + return nil + } +} + +func withShutdownDelay(delay time.Duration) func(data map[string]any) error { + return func(data map[string]any) error { + data["ShutdownDelay"] = delay.String() + return nil + } +} + +func withGracePeriod(period time.Duration) func(data map[string]any) error { + return func(data map[string]any) error { + data["GracePeriod"] = period.String() return nil } } diff --git a/internal/resources/gateways/configuration.go b/internal/resources/gateways/configuration.go index ca4260832..1902ad650 100644 --- a/internal/resources/gateways/configuration.go +++ b/internal/resources/gateways/configuration.go @@ -30,11 +30,27 @@ func createConfigMap(ctx core.Context, stack *v1beta1.Stack, options = append(options, withTrustedProxiesStrict()) } - idleTimeout, err := settings.GetString(ctx, stack.Name, "gateway", "config", "idle-timeout") + shutdownDelay, err := settings.GetDuration(ctx, stack.Name, "gateway", "caddyfile", "shutdown-delay") if err != nil { return nil, err } - if idleTimeout != nil && *idleTimeout != "" { + if shutdownDelay != nil { + options = append(options, withShutdownDelay(*shutdownDelay)) + } + + gracePeriod, err := settings.GetDuration(ctx, stack.Name, "gateway", "caddyfile", "grace-period") + if err != nil { + return nil, err + } + if gracePeriod != nil { + options = append(options, withGracePeriod(*gracePeriod)) + } + + idleTimeout, err := settings.GetDuration(ctx, stack.Name, "gateway", "config", "idle-timeout") + if err != nil { + return nil, err + } + if idleTimeout != nil { options = append(options, withIdleTimeout(*idleTimeout)) } diff --git a/internal/resources/settings/helpers.go b/internal/resources/settings/helpers.go index e85897eab..ddb3593eb 100644 --- a/internal/resources/settings/helpers.go +++ b/internal/resources/settings/helpers.go @@ -9,6 +9,7 @@ import ( "slices" "strconv" "strings" + "time" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" @@ -424,6 +425,33 @@ func GetMapOrEmpty(ctx core.Context, stack string, keys ...string) (map[string]s return value, nil } +func GetDuration(ctx core.Context, stack string, keys ...string) (*time.Duration, error) { + value, err := GetString(ctx, stack, keys...) + if err != nil { + return nil, err + } + + if value == nil { + return nil, nil + } + duration, err := time.ParseDuration(*value) + if err != nil { + return nil, err + } + return &duration, nil +} + +func GetDurationOrDefault(ctx core.Context, stack string, defaultValue time.Duration, keys ...string) (time.Duration, error) { + duration, err := GetDuration(ctx, stack, keys...) + if err != nil { + return 0, err + } + if duration == nil { + return defaultValue, nil + } + return *duration, nil +} + func findMatchingSettings(settings []v1beta1.Settings, flattenKeys ...string) (*string, error) { // Keys can be passed as "a.b.c", instead of "a", "b", "c"