From ce64b2b096788451f8bdb9d6572fba5954b7fd0e Mon Sep 17 00:00:00 2001 From: Marius Zander Date: Mon, 27 Apr 2026 10:39:05 +0200 Subject: [PATCH 1/4] deps: update module github.com/hetznercloud/hcloud-go to timeout-idle pre-release --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4e246c79e..b77399c53 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/fatih/structs v1.1.0 github.com/goccy/go-yaml v1.19.2 github.com/guptarohit/asciigraph v0.9.0 - github.com/hetznercloud/hcloud-go/v2 v2.38.0 + github.com/hetznercloud/hcloud-go/v2 v2.38.1-0.20260424132332-275506f01f2b github.com/jedib0t/go-pretty/v6 v6.7.10 github.com/spf13/cast v1.10.0 github.com/spf13/cobra v1.10.2 diff --git a/go.sum b/go.sum index c5a33130f..4fa7559cf 100644 --- a/go.sum +++ b/go.sum @@ -37,8 +37,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/guptarohit/asciigraph v0.9.0 h1:MvCSRRVkT2XvU1IO6n92o7l7zqx1DiFaoszOUZQztbY= github.com/guptarohit/asciigraph v0.9.0/go.mod h1:dYl5wwK4gNsnFf9Zp+l06rFiDZ5YtXM6x7SRWZ3KGag= -github.com/hetznercloud/hcloud-go/v2 v2.38.0 h1:bJL4O5Zd8iF+vDRzeAbmsMcxKhn/sf/HUtu2jl/bFl0= -github.com/hetznercloud/hcloud-go/v2 v2.38.0/go.mod h1:BHmbGdh59t0CazoUEFvbdp6PyV+gwnF0fl9D4Bdgqq0= +github.com/hetznercloud/hcloud-go/v2 v2.38.1-0.20260424132332-275506f01f2b h1:HFh1KoZ5dwXvBZbskDdPhlhiV/ELp0jd9fPc4OpyQVY= +github.com/hetznercloud/hcloud-go/v2 v2.38.1-0.20260424132332-275506f01f2b/go.mod h1:BHmbGdh59t0CazoUEFvbdp6PyV+gwnF0fl9D4Bdgqq0= github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= From d3d0b4577a4b0db03674c84d8645966218e29bfa Mon Sep 17 00:00:00 2001 From: Marius Zander Date: Mon, 27 Apr 2026 12:27:21 +0200 Subject: [PATCH 2/4] feat(load-balancer): add --http-timeout-idle flag to add-service and update-service --- internal/cmd/loadbalancer/add_service.go | 6 ++++++ internal/cmd/loadbalancer/add_service_test.go | 3 ++- internal/cmd/loadbalancer/update_service.go | 5 +++++ internal/cmd/loadbalancer/update_service_test.go | 2 ++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/internal/cmd/loadbalancer/add_service.go b/internal/cmd/loadbalancer/add_service.go index b48f50521..fd11a3719 100644 --- a/internal/cmd/loadbalancer/add_service.go +++ b/internal/cmd/loadbalancer/add_service.go @@ -34,6 +34,7 @@ var AddServiceCmd = base.Cmd{ cmd.Flags().Duration("http-cookie-lifetime", 0, "Sticky Sessions: Lifetime of the cookie") cmd.Flags().StringSlice("http-certificates", []string{}, "IDs or names of Certificates which should be attached to this Load Balancer") cmd.Flags().Bool("http-redirect-http", false, "Redirect all traffic on port 80 to port 443 (true, false)") + cmd.Flags().Duration("http-timeout-idle", 0, "Idle timeout for HTTP connections (30s-300s)") cmd.Flags().String("health-check-protocol", "", "The protocol the health check is performed over") cmd.Flags().Int("health-check-port", 0, "The port the health check is performed over") @@ -91,6 +92,7 @@ var AddServiceCmd = base.Cmd{ httpCookieName, _ := cmd.Flags().GetString("http-cookie-name") httpCookieLifetime, _ := cmd.Flags().GetDuration("http-cookie-lifetime") httpRedirect, _ := cmd.Flags().GetBool("http-redirect-http") + httpTimeoutIdle, _ := cmd.Flags().GetDuration("http-timeout-idle") loadBalancer, _, err := s.Client().LoadBalancer().Get(s, idOrName) if err != nil { @@ -123,6 +125,10 @@ var AddServiceCmd = base.Cmd{ if httpCookieLifetime != 0 { opts.HTTP.CookieLifetime = &httpCookieLifetime } + if httpTimeoutIdle != 0 { + opts.HTTP.TimeoutIdle = &httpTimeoutIdle + } + for _, idOrName := range httpCertificates { cert, _, err := s.Client().Certificate().Get(s, idOrName) if err != nil { diff --git a/internal/cmd/loadbalancer/add_service_test.go b/internal/cmd/loadbalancer/add_service_test.go index d0d7b8cea..b5ece7c22 100644 --- a/internal/cmd/loadbalancer/add_service_test.go +++ b/internal/cmd/loadbalancer/add_service_test.go @@ -31,6 +31,7 @@ func TestAddService(t *testing.T) { HTTP: &hcloud.LoadBalancerAddServiceOptsHTTP{ StickySessions: hcloud.Ptr(false), RedirectHTTP: hcloud.Ptr(false), + TimeoutIdle: hcloud.Ptr(60 * time.Second), }, Proxyprotocol: hcloud.Ptr(false), }). @@ -39,7 +40,7 @@ func TestAddService(t *testing.T) { WaitForActions(gomock.Any(), gomock.Any(), &hcloud.Action{ID: 123}). Return(nil) - out, errOut, err := fx.Run(cmd, []string{"123", "--protocol", "http", "--listen-port", "80", "--destination-port", "8080"}) + out, errOut, err := fx.Run(cmd, []string{"123", "--protocol", "http", "--listen-port", "80", "--destination-port", "8080", "--http-timeout-idle", "60s"}) expOut := "Service was added to Load Balancer 123\n" diff --git a/internal/cmd/loadbalancer/update_service.go b/internal/cmd/loadbalancer/update_service.go index 309ce43f8..904260ad4 100644 --- a/internal/cmd/loadbalancer/update_service.go +++ b/internal/cmd/loadbalancer/update_service.go @@ -36,6 +36,7 @@ var UpdateServiceCmd = base.Cmd{ cmd.Flags().String("http-cookie-name", "", "Sticky Sessions: Cookie Name which will be set") cmd.Flags().Duration("http-cookie-lifetime", 0, "Sticky Sessions: Lifetime of the cookie") cmd.Flags().StringSlice("http-certificates", []string{}, "IDs or names of Certificates which should be attached to this Load Balancer") + cmd.Flags().Duration("http-timeout-idle", 0, "Idle timeout for HTTP connections (30s-300s)") cmd.Flags().String("health-check-protocol", "", "The protocol the health check is performed over") cmd.Flags().Int("health-check-port", 0, "The port the health check is performed over") @@ -103,6 +104,10 @@ var UpdateServiceCmd = base.Cmd{ cookieLifetime, _ := cmd.Flags().GetDuration("http-cookie-lifetime") opts.HTTP.CookieLifetime = &cookieLifetime } + if cmd.Flag("http-timeout-idle").Changed { + timeoutIdle, _ := cmd.Flags().GetDuration("http-timeout-idle") + opts.HTTP.TimeoutIdle = &timeoutIdle + } if cmd.Flag("http-certificates").Changed { certificates, _ := cmd.Flags().GetStringSlice("http-certificates") for _, idOrName := range certificates { diff --git a/internal/cmd/loadbalancer/update_service_test.go b/internal/cmd/loadbalancer/update_service_test.go index a464be9f9..f7254c7a0 100644 --- a/internal/cmd/loadbalancer/update_service_test.go +++ b/internal/cmd/loadbalancer/update_service_test.go @@ -37,6 +37,7 @@ func TestUpdateService(t *testing.T) { CookieName: hcloud.Ptr("test"), CookieLifetime: hcloud.Ptr(10 * time.Minute), Certificates: []*hcloud.Certificate{{ID: 1}}, + TimeoutIdle: hcloud.Ptr(60 * time.Second), }, HealthCheck: &hcloud.LoadBalancerUpdateServiceOptsHealthCheck{ Protocol: hcloud.LoadBalancerServiceProtocolTCP, @@ -69,6 +70,7 @@ func TestUpdateService(t *testing.T) { "--http-cookie-name", "test", "--http-cookie-lifetime", "10m", "--http-certificates", "1", + "--http-timeout-idle", "60s", "--health-check-protocol", "tcp", "--health-check-port", "8080", "--health-check-interval", "10s", From 823b9f11477529746771cd9c52acafcff783f824 Mon Sep 17 00:00:00 2001 From: Marius Zander Date: Mon, 27 Apr 2026 12:29:41 +0200 Subject: [PATCH 3/4] feat(load-balancer): display http-timeout-idle in describe output --- internal/cmd/loadbalancer/describe.go | 1 + internal/cmd/loadbalancer/describe_test.go | 48 +++++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/internal/cmd/loadbalancer/describe.go b/internal/cmd/loadbalancer/describe.go index e95206dc7..33f03ff10 100644 --- a/internal/cmd/loadbalancer/describe.go +++ b/internal/cmd/loadbalancer/describe.go @@ -76,6 +76,7 @@ var DescribeCmd = base.DescribeCmd[*hcloud.LoadBalancer]{ fmt.Fprintf(out, " Destination Port:\t%d\n", service.DestinationPort) fmt.Fprintf(out, " Proxy Protocol:\t%s\n", util.YesNo(service.Proxyprotocol)) if service.Protocol != hcloud.LoadBalancerServiceProtocolTCP { + fmt.Fprintf(out, " Timeout Idle:\t%vs\n", service.HTTP.TimeoutIdle.Seconds()) fmt.Fprintf(out, " Sticky Sessions:\t%s\n", util.YesNo(service.HTTP.StickySessions)) if service.HTTP.StickySessions { fmt.Fprintf(out, " Sticky Cookie Name:\t%s\n", service.HTTP.CookieName) diff --git a/internal/cmd/loadbalancer/describe_test.go b/internal/cmd/loadbalancer/describe_test.go index 1d0a445a4..3cc4aac94 100644 --- a/internal/cmd/loadbalancer/describe_test.go +++ b/internal/cmd/loadbalancer/describe_test.go @@ -51,6 +51,35 @@ func TestDescribe(t *testing.T) { Algorithm: hcloud.LoadBalancerAlgorithm{ Type: hcloud.LoadBalancerAlgorithmTypeLeastConnections, }, + Services: []hcloud.LoadBalancerService{ + { + Protocol: hcloud.LoadBalancerServiceProtocolHTTP, + ListenPort: 80, + DestinationPort: 8080, + Proxyprotocol: true, + HTTP: hcloud.LoadBalancerServiceHTTP{ + TimeoutIdle: 60 * time.Second, + StickySessions: true, + CookieName: "my-cookie", + CookieLifetime: 5 * time.Minute, + RedirectHTTP: true, + }, + HealthCheck: hcloud.LoadBalancerServiceHealthCheck{ + Protocol: hcloud.LoadBalancerServiceProtocolHTTP, + Port: 8080, + Timeout: 10 * time.Second, + Interval: 15 * time.Second, + Retries: 3, + HTTP: &hcloud.LoadBalancerServiceHealthCheckHTTP{ + Domain: "example.com", + Path: "/health", + Response: "OK", + StatusCodes: []string{"200", "201"}, + TLS: true, + }, + }, + }, + }, IncludedTraffic: 20 * util.Tebibyte, IngoingTraffic: 10 * util.Tebibyte, OutgoingTraffic: 10 * util.Tebibyte, @@ -87,7 +116,24 @@ Load Balancer Type: Max assigned Certificates: 10 Services: - No services + - Protocol: http + Listen Port: 80 + Destination Port: 8080 + Proxy Protocol: yes + Timeout Idle: 60s + Sticky Sessions: yes + Sticky Cookie Name: my-cookie + Sticky Cookie Lifetime: 300s + Health Check: + Protocol: http + Timeout: 10s + Interval: every 15s + Retries: 3 + HTTP Domain: example.com + HTTP Path: /health + Response: OK + TLS: yes + Status Codes: [200 201] Targets: No targets From c3e7ddf411627cca5c87db0d1a5e068067feccab Mon Sep 17 00:00:00 2001 From: Marius Zander Date: Mon, 27 Apr 2026 12:37:32 +0200 Subject: [PATCH 4/4] docs(load-balancer): regenerate --- docs/reference/manual/hcloud_load-balancer_add-service.md | 1 + docs/reference/manual/hcloud_load-balancer_update-service.md | 1 + 2 files changed, 2 insertions(+) diff --git a/docs/reference/manual/hcloud_load-balancer_add-service.md b/docs/reference/manual/hcloud_load-balancer_add-service.md index 268dbd538..dc089bfc1 100644 --- a/docs/reference/manual/hcloud_load-balancer_add-service.md +++ b/docs/reference/manual/hcloud_load-balancer_add-service.md @@ -26,6 +26,7 @@ hcloud load-balancer add-service [options] (--protocol http | --protocol tcp --l --http-cookie-name string Sticky Sessions: Cookie Name we set --http-redirect-http Redirect all traffic on port 80 to port 443 (true, false) --http-sticky-sessions Enable Sticky Sessions (true, false) + --http-timeout-idle duration Idle timeout for HTTP connections (30s-300s) --listen-port int Listen port of the service --protocol string Protocol of the service (required) --proxy-protocol Enable proxyprotocol (true, false) diff --git a/docs/reference/manual/hcloud_load-balancer_update-service.md b/docs/reference/manual/hcloud_load-balancer_update-service.md index 0e65ccd67..f299fa3c0 100644 --- a/docs/reference/manual/hcloud_load-balancer_update-service.md +++ b/docs/reference/manual/hcloud_load-balancer_update-service.md @@ -26,6 +26,7 @@ hcloud load-balancer update-service [options] --listen-port <1-65535>