Skip to content

Commit

Permalink
feat(load-balancer): allow specifying health check options in add-ser…
Browse files Browse the repository at this point in the history
…vice (#743)

Closes #742
  • Loading branch information
phm07 committed May 8, 2024
1 parent dfe103e commit 2cd08b2
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 0 deletions.
88 changes: 88 additions & 0 deletions internal/cmd/loadbalancer/add_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package loadbalancer

import (
"fmt"
"time"

"github.com/spf13/cobra"

Expand Down Expand Up @@ -34,6 +35,18 @@ var AddServiceCmd = base.Cmd{
cmd.Flags().Int64Slice("http-certificates", []int64{}, "ID of Certificates which are attached to this Load Balancer")
cmd.Flags().Bool("http-redirect-http", false, "Redirect all traffic on port 80 to port 443")

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")
cmd.Flags().Duration("health-check-interval", 15*time.Second, "The interval the health check is performed")
cmd.Flags().Duration("health-check-timeout", 10*time.Second, "The timeout after a health check is marked as failed")
cmd.Flags().Int("health-check-retries", 3, "Number of retries after a health check is marked as failed")

cmd.Flags().String("health-check-http-domain", "", "The domain we request when performing a http health check")
cmd.Flags().String("health-check-http-path", "", "The path we request when performing a http health check")
cmd.Flags().StringSlice("health-check-http-status-codes", []string{}, "List of status codes we expect to determine a target as healthy")
cmd.Flags().String("health-check-http-response", "", "The response we expect to determine a target as healthy")
cmd.Flags().Bool("health-check-http-tls", false, "Determine if the health check should verify if the target answers with a valid TLS certificate")

return cmd
},
Run: func(s state.State, cmd *cobra.Command, args []string) error {
Expand Down Expand Up @@ -116,6 +129,81 @@ var AddServiceCmd = base.Cmd{
opts.HTTP.Certificates = append(opts.HTTP.Certificates, &hcloud.Certificate{ID: certificateID})
}
}

// Health check
healthCheckProtocol, _ := cmd.Flags().GetString("health-check-protocol")
healthCheckPort, _ := cmd.Flags().GetInt("health-check-port")
healthCheckInterval, _ := cmd.Flags().GetDuration("health-check-interval")
healthCheckTimeout, _ := cmd.Flags().GetDuration("health-check-timeout")
healthCheckRetries, _ := cmd.Flags().GetInt("health-check-retries")

addHealthCheck := false
for _, f := range []string{"protocol", "port", "interval", "timeout", "retries"} {
if cmd.Flags().Changed("health-check-" + f) {
addHealthCheck = true
break
}
}

if addHealthCheck {
opts.HealthCheck = &hcloud.LoadBalancerAddServiceOptsHealthCheck{}
if healthCheckProtocol == "" {
return fmt.Errorf("required flag health-check-protocol not set")
}
switch proto := hcloud.LoadBalancerServiceProtocol(healthCheckProtocol); proto {
case hcloud.LoadBalancerServiceProtocolHTTP, hcloud.LoadBalancerServiceProtocolHTTPS, hcloud.LoadBalancerServiceProtocolTCP:
opts.HealthCheck.Protocol = proto
break
default:
return fmt.Errorf("invalid health check protocol: %s", healthCheckProtocol)
}

if healthCheckPort == 0 {
return fmt.Errorf("required flag health-check-port not set")
}
if healthCheckPort > 65535 {
return fmt.Errorf("invalid health check port: %d", healthCheckPort)
}
opts.HealthCheck.Port = &healthCheckPort

if cmd.Flags().Changed("health-check-interval") {
opts.HealthCheck.Interval = &healthCheckInterval
}
if cmd.Flags().Changed("health-check-timeout") {
opts.HealthCheck.Timeout = &healthCheckTimeout
}
if cmd.Flags().Changed("health-check-retries") {
opts.HealthCheck.Retries = &healthCheckRetries
}

if opts.HealthCheck.Protocol == hcloud.LoadBalancerServiceProtocolHTTP ||
opts.HealthCheck.Protocol == hcloud.LoadBalancerServiceProtocolHTTPS {

opts.HealthCheck.HTTP = &hcloud.LoadBalancerAddServiceOptsHealthCheckHTTP{}
healthCheckHTTPDomain, _ := cmd.Flags().GetString("health-check-http-domain")
healthCheckHTTPPath, _ := cmd.Flags().GetString("health-check-http-path")
healthCheckHTTPResponse, _ := cmd.Flags().GetString("health-check-http-response")
healthCheckHTTPStatusCodes, _ := cmd.Flags().GetStringSlice("health-check-http-status-codes")
healthCheckHTTPTLS, _ := cmd.Flags().GetBool("health-check-http-tls")

if cmd.Flags().Changed("health-check-http-domain") {
opts.HealthCheck.HTTP.Domain = &healthCheckHTTPDomain
}
if cmd.Flags().Changed("health-check-http-path") {
opts.HealthCheck.HTTP.Path = &healthCheckHTTPPath
}
if cmd.Flags().Changed("health-check-http-response") {
opts.HealthCheck.HTTP.Response = &healthCheckHTTPResponse
}
if cmd.Flags().Changed("health-check-http-status-codes") {
opts.HealthCheck.HTTP.StatusCodes = healthCheckHTTPStatusCodes
}
if cmd.Flags().Changed("health-check-http-tls") {
opts.HealthCheck.HTTP.TLS = &healthCheckHTTPTLS
}
}
}

action, _, err := s.Client().LoadBalancer().AddService(s, loadBalancer, opts)
if err != nil {
return err
Expand Down
73 changes: 73 additions & 0 deletions internal/cmd/loadbalancer/add_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package loadbalancer_test

import (
"testing"
"time"

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -45,3 +46,75 @@ func TestAddService(t *testing.T) {
assert.Empty(t, errOut)
assert.Equal(t, expOut, out)
}

func TestAddServiceWithHealthCheck(t *testing.T) {
fx := testutil.NewFixture(t)
defer fx.Finish()

cmd := loadbalancer.AddServiceCmd.CobraCommand(fx.State())
fx.ExpectEnsureToken()

fx.Client.LoadBalancerClient.EXPECT().
Get(gomock.Any(), "123").
Return(&hcloud.LoadBalancer{ID: 123}, nil, nil)
fx.Client.LoadBalancerClient.EXPECT().
AddService(gomock.Any(), &hcloud.LoadBalancer{ID: 123}, hcloud.LoadBalancerAddServiceOpts{
Protocol: hcloud.LoadBalancerServiceProtocolHTTP,
ListenPort: hcloud.Ptr(80),
DestinationPort: hcloud.Ptr(8080),
HTTP: &hcloud.LoadBalancerAddServiceOptsHTTP{
StickySessions: hcloud.Ptr(true),
RedirectHTTP: hcloud.Ptr(true),
CookieName: hcloud.Ptr("test"),
Certificates: []*hcloud.Certificate{{ID: 1}},
CookieLifetime: hcloud.Ptr(10 * time.Minute),
},
Proxyprotocol: hcloud.Ptr(false),
HealthCheck: &hcloud.LoadBalancerAddServiceOptsHealthCheck{
Protocol: hcloud.LoadBalancerServiceProtocolHTTP,
Port: hcloud.Ptr(80),
Interval: hcloud.Ptr(10 * time.Second),
Timeout: hcloud.Ptr(5 * time.Second),
Retries: hcloud.Ptr(2),
HTTP: &hcloud.LoadBalancerAddServiceOptsHealthCheckHTTP{
Domain: hcloud.Ptr("example.com"),
Path: hcloud.Ptr("/health"),
StatusCodes: []string{"200"},
Response: hcloud.Ptr("OK"),
TLS: hcloud.Ptr(true),
},
},
}).
Return(&hcloud.Action{ID: 123}, nil, nil)
fx.ActionWaiter.EXPECT().
ActionProgress(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",
"--http-redirect-http=true",
"--http-sticky-sessions=true",
"--http-cookie-name", "test",
"--http-cookie-lifetime", "10m",
"--http-certificates", "1",
"--health-check-protocol", "http",
"--health-check-port", "80",
"--health-check-interval", "10s",
"--health-check-timeout", "5s",
"--health-check-retries", "2",
"--health-check-http-domain", "example.com",
"--health-check-http-path", "/health",
"--health-check-http-status-codes", "200",
"--health-check-http-response", "OK",
"--health-check-http-tls=true",
})

expOut := "Service was added to Load Balancer 123\n"

assert.NoError(t, err)
assert.Empty(t, errOut)
assert.Equal(t, expOut, out)
}

0 comments on commit 2cd08b2

Please sign in to comment.