From f7a36c578816859cb3977366bf9c4c34f45b60a7 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Mon, 18 May 2026 14:42:09 -0400 Subject: [PATCH] Add exec probe handler support to probes API Add ProbeHandlerType (HTTP/Exec) and Command/Port/Scheme fields to ProbeConf. Introduce SetProbeConfV2 and CreateProbeSetV2 with a self-contained ProbeConf signature that switches on handler type. Export Merge for downstream use. Existing SetProbeConf/CreateProbeSet delegate to V2 for full backward compatibility. References: OSPRH-30190 Co-Authored-By: Claude Opus 4.6 --- modules/common/probes/probes.go | 201 +++++---- modules/common/probes/probes_test.go | 398 +++++++++++++++++- modules/common/probes/types.go | 25 +- .../common/probes/zz_generated.deepcopy.go | 16 +- modules/common/util/errors.go | 2 + 5 files changed, 542 insertions(+), 100 deletions(-) diff --git a/modules/common/probes/probes.go b/modules/common/probes/probes.go index e2d36271..8b477963 100644 --- a/modules/common/probes/probes.go +++ b/modules/common/probes/probes.go @@ -30,12 +30,23 @@ import ( "strings" ) -func (p *ProbeConf) merge(overrides ProbeConf) { - // Override path if provided +// Merge applies non-zero override values onto the receiver +func (p *ProbeConf) Merge(overrides ProbeConf) { + if overrides.Type != "" { + p.Type = overrides.Type + } if overrides.Path != "" { p.Path = overrides.Path } - // Override timing values if they are non-zero + if len(overrides.Command) > 0 { + p.Command = overrides.Command + } + if overrides.Port > 0 { + p.Port = overrides.Port + } + if overrides.Scheme != nil { + p.Scheme = overrides.Scheme + } if overrides.InitialDelaySeconds > 0 { p.InitialDelaySeconds = overrides.InitialDelaySeconds } @@ -50,70 +61,61 @@ func (p *ProbeConf) merge(overrides ProbeConf) { } } -// CreateProbeSet - creates all probes at once using the interface +// CreateProbeSet creates all probes at once using the interface. +// Port and scheme are applied to all probes as HTTP GET handler parameters. +// For mixed probe types (e.g. HTTP and exec), use CreateProbeSetV2 instead +// with Port/Scheme set in the individual ProbeConf defaults. func CreateProbeSet( port int32, scheme *v1.URIScheme, overrides ProbeOverrides, defaults OverrideSpec, ) (*ProbeSet, error) { + for _, p := range []*ProbeConf{defaults.LivenessProbes, defaults.ReadinessProbes, defaults.StartupProbes} { + if p != nil { + p.Port = port + p.Scheme = scheme + } + } + return CreateProbeSetV2(overrides, defaults) +} - livenessProbe, err := SetProbeConf( - port, - scheme, - func() ProbeConf { - if defaults.LivenessProbes == nil { - defaults.LivenessProbes = &ProbeConf{} - } - baseConf := *defaults.LivenessProbes - if p := overrides.GetLivenessProbes(); p != nil { - baseConf.merge(*p) - } - return baseConf - }(), - ) +// CreateProbeSetV2 creates all probes at once using the interface. +// Each probe's handler type, port, scheme, path, and command are determined +// by the ProbeConf fields in the defaults and overrides. +func CreateProbeSetV2( + overrides ProbeOverrides, + defaults OverrideSpec, +) (*ProbeSet, error) { - // Could not process probes config + mergeConf := func(base *ProbeConf, override *ProbeConf) ProbeConf { + if base == nil { + base = &ProbeConf{} + } + conf := *base + if override != nil { + conf.Merge(*override) + } + return conf + } + + livenessProbe, err := SetProbeConfV2( + mergeConf(defaults.LivenessProbes, overrides.GetLivenessProbes()), + ) if err != nil { return nil, err } - readinessProbe, err := SetProbeConf( - port, - scheme, - func() ProbeConf { - if defaults.ReadinessProbes == nil { - defaults.ReadinessProbes = &ProbeConf{} - } - baseConf := *defaults.ReadinessProbes - if p := overrides.GetReadinessProbes(); p != nil { - baseConf.merge(*p) - } - return baseConf - }(), + readinessProbe, err := SetProbeConfV2( + mergeConf(defaults.ReadinessProbes, overrides.GetReadinessProbes()), ) - - // Could not process probes config if err != nil { return nil, err } - startupProbe, err := SetProbeConf( - port, - scheme, - func() ProbeConf { - if defaults.StartupProbes == nil { - defaults.StartupProbes = &ProbeConf{} - } - baseConf := *defaults.StartupProbes - if p := overrides.GetStartupProbes(); p != nil { - baseConf.merge(*p) - } - return baseConf - }(), + startupProbe, err := SetProbeConfV2( + mergeConf(defaults.StartupProbes, overrides.GetStartupProbes()), ) - - // Could not process probes config if err != nil { return nil, err } @@ -125,72 +127,99 @@ func CreateProbeSet( }, nil } -// SetProbeConf configures and returns liveness and readiness probes based on -// the provided settings +// SetProbeConf configures and returns an HTTP GET probe based on the provided +// settings. For exec-based probes, use SetProbeConfV2 instead. func SetProbeConf(port int32, scheme *v1.URIScheme, config ProbeConf) (*v1.Probe, error) { - if port < 1 || port > 65535 { - return nil, fmt.Errorf("%w: %d", util.ErrInvalidPort, port) + config.Port = port + config.Scheme = scheme + if config.Type == "" { + config.Type = ProbeHandlerHTTP } + return SetProbeConfV2(config) +} + +// SetProbeConfV2 configures and returns a probe based on the ProbeConf +// settings. The probe handler type is determined by ProbeConf.Type: +// ProbeHandlerExec creates an exec probe using ProbeConf.Command, while +// ProbeHandlerHTTP (or unset) creates an HTTP GET probe using ProbeConf.Port, +// ProbeConf.Path, and ProbeConf.Scheme. +func SetProbeConfV2(config ProbeConf) (*v1.Probe, error) { probe := &v1.Probe{ - ProbeHandler: v1.ProbeHandler{ - HTTPGet: &v1.HTTPGetAction{ - Path: config.Path, - Port: intstr.FromInt32(port), - }, - }, InitialDelaySeconds: config.InitialDelaySeconds, TimeoutSeconds: config.TimeoutSeconds, PeriodSeconds: config.PeriodSeconds, FailureThreshold: config.FailureThreshold, } - if scheme != nil { - probe.HTTPGet.Scheme = *scheme + + switch config.Type { + case ProbeHandlerExec: + if len(config.Command) == 0 { + return nil, util.ErrExecProbeCommandRequired + } + probe.ProbeHandler = v1.ProbeHandler{ + Exec: &v1.ExecAction{ + Command: config.Command, + }, + } + default: + if config.Port < 1 || config.Port > 65535 { + return nil, fmt.Errorf("%w: %d", util.ErrInvalidPort, config.Port) + } + probe.ProbeHandler = v1.ProbeHandler{ + HTTPGet: &v1.HTTPGetAction{ + Path: config.Path, + Port: intstr.FromInt32(config.Port), + }, + } + if config.Scheme != nil { + probe.HTTPGet.Scheme = *config.Scheme + } } return probe, nil } -// ValidateProbeConf - This function can be used at webhooks level to explicitly -// validate the overrides +// ValidateProbeConf validates probe configuration overrides for use at the +// webhook level func ValidateProbeConf(basePath *field.Path, config *ProbeConf) field.ErrorList { errorList := field.ErrorList{} - // nothing to validate, return an empty errorList if config == nil { return errorList } - // Path validation: fail is explicitly set as an empty string - // or the endpoint does't start with "/" - if config.Path != "" && !strings.HasPrefix(config.Path, "/") { - err := field.Invalid(basePath.Child("path"), config.Path, - "path must start with '/' if specified") - errorList = append(errorList, err) + + switch config.Type { + case ProbeHandlerExec: + if len(config.Command) == 0 { + errorList = append(errorList, field.Required(basePath.Child("command"), + "command is required for exec probe type")) + } + case "", ProbeHandlerHTTP: + if config.Path != "" && !strings.HasPrefix(config.Path, "/") { + errorList = append(errorList, field.Invalid(basePath.Child("path"), config.Path, + "path must start with '/' if specified")) + } + default: + errorList = append(errorList, field.Invalid(basePath.Child("type"), config.Type, + "type must be one of: HTTP, Exec, or unset")) } - // InitialDelaySeconds validation: must be > 0 if config.InitialDelaySeconds < 0 { - err := field.Invalid(basePath.Child("initialDelaySeconds"), config.InitialDelaySeconds, - "initialDelaySeconds must be non-negative") - errorList = append(errorList, err) + errorList = append(errorList, field.Invalid(basePath.Child("initialDelaySeconds"), config.InitialDelaySeconds, + "initialDelaySeconds must be non-negative")) } - // TimeoutSeconds validation: fail if it's a negative number if config.TimeoutSeconds != 0 && config.TimeoutSeconds < 1 { - err := field.Invalid(basePath.Child("timeoutSeconds"), config.TimeoutSeconds, - "timeoutSeconds must be at least 1 second when set") - errorList = append(errorList, err) + errorList = append(errorList, field.Invalid(basePath.Child("timeoutSeconds"), config.TimeoutSeconds, + "timeoutSeconds must be at least 1 second when set")) } - // PeriodSeconds validation: fail if it's set as a negative number if config.PeriodSeconds != 0 && config.PeriodSeconds < 1 { - err := field.Invalid(basePath.Child("periodSeconds"), config.PeriodSeconds, - "periodSeconds must be at least 1 second when set") - errorList = append(errorList, err) + errorList = append(errorList, field.Invalid(basePath.Child("periodSeconds"), config.PeriodSeconds, + "periodSeconds must be at least 1 second when set")) } - // FailureThreshold validation: fail if it's set as a negative number if config.FailureThreshold != 0 && config.FailureThreshold < 1 { - err := field.Invalid(basePath.Child("failureThreshold"), config.FailureThreshold, - "failureThreshold must be at least 1 when set") - errorList = append(errorList, err) + errorList = append(errorList, field.Invalid(basePath.Child("failureThreshold"), config.FailureThreshold, + "failureThreshold must be at least 1 when set")) } return errorList diff --git a/modules/common/probes/probes_test.go b/modules/common/probes/probes_test.go index 37471455..e8166b44 100644 --- a/modules/common/probes/probes_test.go +++ b/modules/common/probes/probes_test.go @@ -20,6 +20,7 @@ import ( "testing" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/validation/field" ) func TestSetProbeConf(t *testing.T) { @@ -383,24 +384,69 @@ func TestCreateProbeSetWithActualOverrides(t *testing.T) { t.Fatalf("CreateProbeSet() unexpected error: %v", err) } // Validate liveness probe - validateProbe(t, "liveness", probeSet.Liveness, tt.expectedLiveness) + validateHTTPProbe(t, "liveness", probeSet.Liveness, tt.expectedLiveness) // Validate readiness probe - validateProbe(t, "readiness", probeSet.Readiness, tt.expectedReadiness) + validateHTTPProbe(t, "readiness", probeSet.Readiness, tt.expectedReadiness) // Validate startup probe - validateProbe(t, "startup", probeSet.Startup, tt.expectedStartup) + validateHTTPProbe(t, "startup", probeSet.Startup, tt.expectedStartup) }) } } -func validateProbe(t *testing.T, probeType string, actual *v1.Probe, expected ProbeConf) { +func validateHTTPProbe(t *testing.T, probeType string, actual *v1.Probe, expected ProbeConf) { t.Helper() if actual == nil { - t.Errorf("%s probe is nil", probeType) - return + t.Fatalf("%s probe is nil", probeType) + } + if actual.HTTPGet == nil { + t.Fatalf("%s probe HTTPGet is nil", probeType) + } + if actual.Exec != nil { + t.Errorf("%s probe Exec should be nil for HTTP probe", probeType) } if actual.HTTPGet.Path != expected.Path { t.Errorf("%s probe path = %q, want %q", probeType, actual.HTTPGet.Path, expected.Path) } + if expected.Port != 0 && actual.HTTPGet.Port.IntValue() != int(expected.Port) { + t.Errorf("%s probe port = %d, want %d", probeType, actual.HTTPGet.Port.IntValue(), expected.Port) + } + if expected.Scheme != nil && actual.HTTPGet.Scheme != *expected.Scheme { + t.Errorf("%s probe scheme = %v, want %v", probeType, actual.HTTPGet.Scheme, *expected.Scheme) + } + if actual.InitialDelaySeconds != expected.InitialDelaySeconds { + t.Errorf("%s probe initialDelaySeconds = %d, want %d", probeType, actual.InitialDelaySeconds, expected.InitialDelaySeconds) + } + if actual.TimeoutSeconds != expected.TimeoutSeconds { + t.Errorf("%s probe timeoutSeconds = %d, want %d", probeType, actual.TimeoutSeconds, expected.TimeoutSeconds) + } + if actual.PeriodSeconds != expected.PeriodSeconds { + t.Errorf("%s probe periodSeconds = %d, want %d", probeType, actual.PeriodSeconds, expected.PeriodSeconds) + } + if actual.FailureThreshold != expected.FailureThreshold { + t.Errorf("%s probe failureThreshold = %d, want %d", probeType, actual.FailureThreshold, expected.FailureThreshold) + } +} + +func validateExecProbe(t *testing.T, probeType string, actual *v1.Probe, expected ProbeConf) { + t.Helper() + if actual == nil { + t.Fatalf("%s probe is nil", probeType) + } + if actual.Exec == nil { + t.Fatalf("%s probe Exec is nil", probeType) + } + if actual.HTTPGet != nil { + t.Errorf("%s probe HTTPGet should be nil for exec probe", probeType) + } + if len(actual.Exec.Command) != len(expected.Command) { + t.Errorf("%s probe command length = %d, want %d", probeType, len(actual.Exec.Command), len(expected.Command)) + } else { + for i, cmd := range actual.Exec.Command { + if cmd != expected.Command[i] { + t.Errorf("%s probe command[%d] = %q, want %q", probeType, i, cmd, expected.Command[i]) + } + } + } if actual.InitialDelaySeconds != expected.InitialDelaySeconds { t.Errorf("%s probe initialDelaySeconds = %d, want %d", probeType, actual.InitialDelaySeconds, expected.InitialDelaySeconds) } @@ -414,3 +460,343 @@ func validateProbe(t *testing.T, probeType string, actual *v1.Probe, expected Pr t.Errorf("%s probe failureThreshold = %d, want %d", probeType, actual.FailureThreshold, expected.FailureThreshold) } } + +func TestSetProbeConfV2ExecProbe(t *testing.T) { + config := ProbeConf{ + Type: ProbeHandlerExec, + Command: []string{"/bin/sh", "-c", "mysqladmin ping"}, + InitialDelaySeconds: 10, + TimeoutSeconds: 5, + PeriodSeconds: 15, + FailureThreshold: 3, + } + + probe, err := SetProbeConfV2(config) + if err != nil { + t.Fatalf("SetProbeConfV2() unexpected error: %v", err) + } + validateExecProbe(t, "exec", probe, config) +} + +func TestSetProbeConfV2HTTPProbe(t *testing.T) { + scheme := v1.URISchemeHTTPS + config := ProbeConf{ + Type: ProbeHandlerHTTP, + Path: "/healthz", + Port: 8443, + Scheme: &scheme, + InitialDelaySeconds: 30, + TimeoutSeconds: 5, + PeriodSeconds: 10, + FailureThreshold: 3, + } + + probe, err := SetProbeConfV2(config) + if err != nil { + t.Fatalf("SetProbeConfV2() unexpected error: %v", err) + } + validateHTTPProbe(t, "http", probe, config) +} + +func TestSetProbeConfV2DefaultType(t *testing.T) { + config := ProbeConf{ + Path: "/health", + Port: 8080, + } + + probe, err := SetProbeConfV2(config) + if err != nil { + t.Fatalf("SetProbeConfV2() unexpected error: %v", err) + } + if probe.HTTPGet == nil { + t.Fatal("SetProbeConfV2() should default to HTTP when type is empty") + } +} + +func TestSetProbeConfV2Errors(t *testing.T) { + tests := []struct { + name string + config ProbeConf + }{ + { + name: "Exec with empty command", + config: ProbeConf{ + Type: ProbeHandlerExec, + }, + }, + { + name: "HTTP with invalid port", + config: ProbeConf{ + Type: ProbeHandlerHTTP, + Port: 0, + Path: "/health", + }, + }, + { + name: "HTTP with port too large", + config: ProbeConf{ + Type: ProbeHandlerHTTP, + Port: 70000, + Path: "/health", + }, + }, + { + name: "Default type with zero port", + config: ProbeConf{ + Path: "/health", + Port: 0, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := SetProbeConfV2(tt.config) + if err == nil { + t.Error("SetProbeConfV2() expected error but got none") + } + }) + } +} + +func TestMergeHTTPOverrides(t *testing.T) { + scheme := v1.URISchemeHTTPS + base := ProbeConf{ + Type: ProbeHandlerHTTP, + Path: "/healthcheck", + Port: 8080, + TimeoutSeconds: 5, + PeriodSeconds: 10, + FailureThreshold: 3, + } + + overrides := ProbeConf{ + Path: "/healthcheck/v2", + Port: 8443, + Scheme: &scheme, + TimeoutSeconds: 10, + } + + base.Merge(overrides) + + if base.Type != ProbeHandlerHTTP { + t.Errorf("Merge() type = %q, want %q", base.Type, ProbeHandlerHTTP) + } + if base.Path != "/healthcheck/v2" { + t.Errorf("Merge() path = %q, want /healthcheck/v2", base.Path) + } + if base.Port != 8443 { + t.Errorf("Merge() port = %d, want 8443", base.Port) + } + if base.Scheme == nil || *base.Scheme != v1.URISchemeHTTPS { + t.Errorf("Merge() scheme = %v, want HTTPS", base.Scheme) + } + if base.TimeoutSeconds != 10 { + t.Errorf("Merge() timeoutSeconds = %d, want 10", base.TimeoutSeconds) + } + if base.PeriodSeconds != 10 { + t.Errorf("Merge() periodSeconds = %d, want 10 (should preserve non-overridden)", base.PeriodSeconds) + } + if base.FailureThreshold != 3 { + t.Errorf("Merge() failureThreshold = %d, want 3 (should preserve non-overridden)", base.FailureThreshold) + } +} + +func TestMergeExecCommandOverride(t *testing.T) { + base := ProbeConf{ + Type: ProbeHandlerExec, + Command: []string{"/bin/sh", "-c", "mysqladmin ping"}, + TimeoutSeconds: 5, + PeriodSeconds: 10, + FailureThreshold: 3, + } + + overrides := ProbeConf{ + Command: []string{"/bin/sh", "-c", "mysqladmin ping --connect-timeout=10"}, + TimeoutSeconds: 15, + } + + base.Merge(overrides) + + if base.Type != ProbeHandlerExec { + t.Errorf("Merge() type = %q, want %q", base.Type, ProbeHandlerExec) + } + if len(base.Command) != 3 || base.Command[2] != "mysqladmin ping --connect-timeout=10" { + t.Errorf("Merge() command = %v, want [/bin/sh -c mysqladmin ping --connect-timeout=10]", base.Command) + } + if base.TimeoutSeconds != 15 { + t.Errorf("Merge() timeoutSeconds = %d, want 15", base.TimeoutSeconds) + } + if base.PeriodSeconds != 10 { + t.Errorf("Merge() periodSeconds = %d, want 10 (should preserve non-overridden)", base.PeriodSeconds) + } +} + +func TestMergeExecTimingOnly(t *testing.T) { + base := ProbeConf{ + Type: ProbeHandlerExec, + Command: []string{"/bin/sh", "-c", "mysqladmin ping"}, + TimeoutSeconds: 5, + PeriodSeconds: 10, + FailureThreshold: 3, + } + + overrides := ProbeConf{ + TimeoutSeconds: 30, + FailureThreshold: 6, + } + + base.Merge(overrides) + + if base.Type != ProbeHandlerExec { + t.Errorf("Merge() type = %q, want %q", base.Type, ProbeHandlerExec) + } + if base.Command[2] != "mysqladmin ping" { + t.Errorf("Merge() command should be preserved, got %v", base.Command) + } + if base.TimeoutSeconds != 30 { + t.Errorf("Merge() timeoutSeconds = %d, want 30", base.TimeoutSeconds) + } + if base.FailureThreshold != 6 { + t.Errorf("Merge() failureThreshold = %d, want 6", base.FailureThreshold) + } + if base.PeriodSeconds != 10 { + t.Errorf("Merge() periodSeconds = %d, want 10 (should preserve non-overridden)", base.PeriodSeconds) + } +} + +func TestMergeEmptyOverrides(t *testing.T) { + base := ProbeConf{ + Type: ProbeHandlerExec, + Command: []string{"/bin/sh", "-c", "mysqladmin ping"}, + TimeoutSeconds: 5, + PeriodSeconds: 10, + FailureThreshold: 3, + } + + base.Merge(ProbeConf{}) + + if base.Type != ProbeHandlerExec { + t.Errorf("Merge() type should not be overwritten by empty, got %q", base.Type) + } + if len(base.Command) != 3 { + t.Errorf("Merge() command should not be overwritten by nil, got %v", base.Command) + } + if base.TimeoutSeconds != 5 { + t.Errorf("Merge() timeoutSeconds should not be overwritten by zero, got %d", base.TimeoutSeconds) + } +} + +func TestCreateProbeSetV2(t *testing.T) { + defaults := OverrideSpec{ + LivenessProbes: &ProbeConf{ + Type: ProbeHandlerHTTP, + Path: "/healthz", + Port: 8080, + TimeoutSeconds: 5, + PeriodSeconds: 10, + FailureThreshold: 3, + }, + ReadinessProbes: &ProbeConf{ + Type: ProbeHandlerExec, + Command: []string{"/bin/sh", "-c", "mysql -e 'SELECT 1'"}, + TimeoutSeconds: 3, + PeriodSeconds: 5, + FailureThreshold: 1, + }, + StartupProbes: &ProbeConf{ + Type: ProbeHandlerExec, + Command: []string{"/bin/sh", "-c", "mysql -e 'SELECT 1'"}, + TimeoutSeconds: 10, + PeriodSeconds: 5, + FailureThreshold: 10, + }, + } + + overrides := OverrideSpec{ + LivenessProbes: &ProbeConf{ + TimeoutSeconds: 15, + }, + StartupProbes: &ProbeConf{ + FailureThreshold: 20, + }, + } + + probeSet, err := CreateProbeSetV2(overrides, defaults) + if err != nil { + t.Fatalf("CreateProbeSetV2() unexpected error: %v", err) + } + + validateHTTPProbe(t, "liveness", probeSet.Liveness, ProbeConf{ + Path: "/healthz", + Port: 8080, + TimeoutSeconds: 15, + PeriodSeconds: 10, + FailureThreshold: 3, + }) + validateExecProbe(t, "readiness", probeSet.Readiness, *defaults.ReadinessProbes) + validateExecProbe(t, "startup", probeSet.Startup, ProbeConf{ + Command: []string{"/bin/sh", "-c", "mysql -e 'SELECT 1'"}, + TimeoutSeconds: 10, + PeriodSeconds: 5, + FailureThreshold: 20, + }) +} + +func TestValidateProbeConfExec(t *testing.T) { + tests := []struct { + name string + config *ProbeConf + wantCount int + }{ + { + name: "Valid exec probe", + config: &ProbeConf{ + Type: ProbeHandlerExec, + Command: []string{"/bin/check"}, + TimeoutSeconds: 5, + }, + wantCount: 0, + }, + { + name: "Exec probe missing command", + config: &ProbeConf{ + Type: ProbeHandlerExec, + }, + wantCount: 1, + }, + { + name: "Invalid type", + config: &ProbeConf{ + Type: "BadType", + }, + wantCount: 1, + }, + { + name: "Exec probe with invalid timing", + config: &ProbeConf{ + Type: ProbeHandlerExec, + Command: []string{"/bin/check"}, + InitialDelaySeconds: -1, + }, + wantCount: 1, + }, + { + name: "Exec probe ignores path validation", + config: &ProbeConf{ + Type: ProbeHandlerExec, + Command: []string{"/bin/check"}, + Path: "no-leading-slash", + }, + wantCount: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + errs := ValidateProbeConf(field.NewPath("spec"), tt.config) + if len(errs) != tt.wantCount { + t.Errorf("ValidateProbeConf() error count = %d, want %d: %v", len(errs), tt.wantCount, errs) + } + }) + } +} diff --git a/modules/common/probes/types.go b/modules/common/probes/types.go index dd474439..4445b6b9 100644 --- a/modules/common/probes/types.go +++ b/modules/common/probes/types.go @@ -24,15 +24,30 @@ import ( "k8s.io/apimachinery/pkg/util/validation/field" ) +// ProbeHandlerType defines the type of probe handler +// +kubebuilder:validation:Enum=HTTP;Exec;"" +type ProbeHandlerType string + +const ( + // ProbeHandlerHTTP configures an HTTP GET probe handler + ProbeHandlerHTTP ProbeHandlerType = "HTTP" + // ProbeHandlerExec configures an exec probe handler + ProbeHandlerExec ProbeHandlerType = "Exec" +) + // ProbeConf - the configuration for liveness and readiness probes -// LivenessPath - Endpoint path for the liveness probe -// ReadinessPath - Endpoint path for the readiness probe -// InitialDelaySeconds - Number of seconds after the container starts before liveness/readiness probes are initiated -// TimeoutSeconds - Number of seconds after which the probe times out -// PeriodSeconds - How often (in seconds) to perform the probe type ProbeConf struct { + // +kubebuilder:validation:Optional + Type ProbeHandlerType `json:"type,omitempty"` // +kubebuilder:validation:Pattern=`^(/.*)?$` Path string `json:"path,omitempty"` + // +listType=atomic + Command []string `json:"command,omitempty"` + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 + Port int32 `json:"port,omitempty"` + // +kubebuilder:validation:Optional + Scheme *v1.URIScheme `json:"scheme,omitempty"` // +kubebuilder:validation:Minimum=0 InitialDelaySeconds int32 `json:"initialDelaySeconds,omitempty"` // +kubebuilder:validation:Minimum=1 diff --git a/modules/common/probes/zz_generated.deepcopy.go b/modules/common/probes/zz_generated.deepcopy.go index f3f3cf0a..fd8a98d5 100644 --- a/modules/common/probes/zz_generated.deepcopy.go +++ b/modules/common/probes/zz_generated.deepcopy.go @@ -30,17 +30,17 @@ func (in *OverrideSpec) DeepCopyInto(out *OverrideSpec) { if in.LivenessProbes != nil { in, out := &in.LivenessProbes, &out.LivenessProbes *out = new(ProbeConf) - **out = **in + (*in).DeepCopyInto(*out) } if in.ReadinessProbes != nil { in, out := &in.ReadinessProbes, &out.ReadinessProbes *out = new(ProbeConf) - **out = **in + (*in).DeepCopyInto(*out) } if in.StartupProbes != nil { in, out := &in.StartupProbes, &out.StartupProbes *out = new(ProbeConf) - **out = **in + (*in).DeepCopyInto(*out) } } @@ -57,6 +57,16 @@ func (in *OverrideSpec) DeepCopy() *OverrideSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ProbeConf) DeepCopyInto(out *ProbeConf) { *out = *in + if in.Command != nil { + in, out := &in.Command, &out.Command + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Scheme != nil { + in, out := &in.Scheme, &out.Scheme + *out = new(v1.URIScheme) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProbeConf. diff --git a/modules/common/util/errors.go b/modules/common/util/errors.go index 50a681a8..f7edb70c 100644 --- a/modules/common/util/errors.go +++ b/modules/common/util/errors.go @@ -31,4 +31,6 @@ var ( ErrInstanceTypeUnsetWithMultiTemplateDir = errors.New("instance type not set") // ErrCommonTemplateNotFound indicates a requested common template does not exist ErrCommonTemplateNotFound = errors.New("common template not found") + // ErrExecProbeCommandRequired indicates that an exec probe has no command + ErrExecProbeCommandRequired = errors.New("exec probe requires a non-empty command") )