Compare commits
No commits in common. "e830e255608756591897fae7ba658170a02e28a3" and "24cb5d816a353aee3968da44b1392031d5b554b2" have entirely different histories.
e830e25560
...
24cb5d816a
@ -5,6 +5,7 @@ when:
|
||||
|
||||
steps:
|
||||
test:
|
||||
group: test
|
||||
image: golang:1.22-alpine
|
||||
commands:
|
||||
- go test -v ./...
|
||||
|
83
CHANGELOG.md
83
CHANGELOG.md
@ -1,86 +1,3 @@
|
||||
## [1.3.1-rc.11](https://git.ext.icikowski.pl/go/kubeprobes/compare/v1.3.1-rc.10...v1.3.1-rc.11) (2024-05-27)
|
||||
|
||||
|
||||
### Refactoring
|
||||
|
||||
* **formatting:** change line terminators from CRLF to LF ([ec44d7f](https://git.ext.icikowski.pl/go/kubeprobes/commit/ec44d7f643e78cfb1e9724b36416458782a6c775))
|
||||
|
||||
## [1.3.1-rc.10](https://git.ext.icikowski.pl/go/kubeprobes/compare/v1.3.1-rc.9...v1.3.1-rc.10) (2024-05-27)
|
||||
|
||||
|
||||
### Continuous Integrations
|
||||
|
||||
* **test:** remove `group` directive ([a1bab99](https://git.ext.icikowski.pl/go/kubeprobes/commit/a1bab99cbdbf903256be3172036fc9b8c44f9992))
|
||||
|
||||
## [1.3.1-rc.9](https://git.ext.icikowski.pl/go/kubeprobes/compare/v1.3.1-rc.8...v1.3.1-rc.9) (2024-05-21)
|
||||
|
||||
|
||||
### Continuous Integrations
|
||||
|
||||
* **deps:** pin `conventional-changelog-conventionalcommits` to version 7.x ([b15846a](https://git.ext.icikowski.pl/go/kubeprobes/commit/b15846aa58864f5809e2226cad9c52770f84aae9))
|
||||
|
||||
|
||||
### Build system and dependencies
|
||||
|
||||
* **deps:** update all non-major dependencies ([a68663c](https://git.ext.icikowski.pl/go/kubeprobes/commit/a68663cff022fa135d48fae6f00e39ea684983f1))
|
||||
* **deps:** update dependency conventional-changelog-conventionalcommits to v8 ([8918072](https://git.ext.icikowski.pl/go/kubeprobes/commit/8918072d5c818bad10542957b93440730d50baf1))
|
||||
|
||||
## [1.3.1-rc.8](https://git.ext.icikowski.pl/go/kubeprobes/compare/v1.3.1-rc.7...v1.3.1-rc.8) (2024-04-12)
|
||||
|
||||
|
||||
### Build system and dependencies
|
||||
|
||||
* **deps:** update dependency semantic-release to v23.0.8 ([b98f6ef](https://git.ext.icikowski.pl/go/kubeprobes/commit/b98f6ef609c278bfbd25c25316ad166fba75f255))
|
||||
|
||||
## [1.3.1-rc.7](https://git.ext.icikowski.pl/go/kubeprobes/compare/v1.3.1-rc.6...v1.3.1-rc.7) (2024-04-12)
|
||||
|
||||
|
||||
### Build system and dependencies
|
||||
|
||||
* **deps:** update dependency @semantic-release/release-notes-generator to v13 ([0cba4d2](https://git.ext.icikowski.pl/go/kubeprobes/commit/0cba4d2a50319cc0250dc3cb14e32dee813b4fc0))
|
||||
|
||||
## [1.3.1-rc.6](https://git.ext.icikowski.pl/go/kubeprobes/compare/v1.3.1-rc.5...v1.3.1-rc.6) (2024-03-23)
|
||||
|
||||
|
||||
### Build system and dependencies
|
||||
|
||||
* **deps:** update dependency semantic-release to v23.0.5 ([061100e](https://git.ext.icikowski.pl/go/kubeprobes/commit/061100e432a61ed6bcb310de297880db369e2f24))
|
||||
|
||||
## [1.3.1-rc.5](https://git.ext.icikowski.pl/go/kubeprobes/compare/v1.3.1-rc.4...v1.3.1-rc.5) (2024-03-23)
|
||||
|
||||
|
||||
### Build system and dependencies
|
||||
|
||||
* **deps:** update dependency @semantic-release/commit-analyzer to v12 ([2a79a68](https://git.ext.icikowski.pl/go/kubeprobes/commit/2a79a6878f647049ca2f01711d74561f6c4974c6))
|
||||
|
||||
## [1.3.1-rc.4](https://git.ext.icikowski.pl/go/kubeprobes/compare/v1.3.1-rc.3...v1.3.1-rc.4) (2024-03-17)
|
||||
|
||||
|
||||
### Build system and dependencies
|
||||
|
||||
* **deps:** update dependency @semantic-release/npm to v12 ([fdee33a](https://git.ext.icikowski.pl/go/kubeprobes/commit/fdee33a1e7c05bd19f0f5d675df434ad4d844911))
|
||||
|
||||
## [1.3.1-rc.3](https://git.ext.icikowski.pl/go/kubeprobes/compare/v1.3.1-rc.2...v1.3.1-rc.3) (2024-03-17)
|
||||
|
||||
|
||||
### Build system and dependencies
|
||||
|
||||
* **deps:** update dependency semantic-release to v23.0.4 ([2d5b101](https://git.ext.icikowski.pl/go/kubeprobes/commit/2d5b101b8d38d7af7c6b4ba570f1045fe9e9dc7b))
|
||||
|
||||
## [1.3.1-rc.2](https://git.ext.icikowski.pl/go/kubeprobes/compare/v1.3.1-rc.1...v1.3.1-rc.2) (2024-03-16)
|
||||
|
||||
|
||||
### Build system and dependencies
|
||||
|
||||
* **deps:** update dependency semantic-release to v23.0.3 ([cea539f](https://git.ext.icikowski.pl/go/kubeprobes/commit/cea539fd6d68608635ec19ae8840da749695bfac))
|
||||
|
||||
## [1.3.1-rc.1](https://git.ext.icikowski.pl/go/kubeprobes/compare/v1.3.0...v1.3.1-rc.1) (2024-03-03)
|
||||
|
||||
|
||||
### Refactoring
|
||||
|
||||
* **probes:** rename `ProbeFunction` to `Probe` ([8dc7f27](https://git.ext.icikowski.pl/go/kubeprobes/commit/8dc7f27400075fabca9525f42eb20404736fb1cb))
|
||||
|
||||
## [1.3.0](https://git.ext.icikowski.pl/go/kubeprobes/compare/v1.2.0...v1.3.0) (2024-03-02)
|
||||
|
||||
|
||||
|
28
README.md
28
README.md
@ -1,7 +1,5 @@
|
||||
# kubeprobes
|
||||
|
||||
[![Go Report Card](https://goreportcard.com/badge/pkg.icikowski.pl/kubeprobes)](https://goreportcard.com/report/pkg.icikowski.pl/kubeprobes)
|
||||
|
||||
Simple and effective package for implementing [Kubernetes liveness and readiness probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/)' handler.
|
||||
|
||||
## Installation
|
||||
@ -23,22 +21,22 @@ Default paths can be overriden with options described below. Accessing any other
|
||||
|
||||
The `kubeprobes.New` function accepts following options as arguments:
|
||||
|
||||
- `kubeprobes.WithLivenessProbes(...)` - adds particular [probes](#probes) to the list of liveness probes;
|
||||
- `kubeprobes.WithLivenessProbes(...)` - adds particular [probe functions](#probe-functions) to the list of liveness probes;
|
||||
- `kubeprobes.WithLivenessPath("/some/liveness/path")` - sets liveness probe path to given path (default is `/live`);
|
||||
- `kubeprobes.WithReadinessProbes(...)` - adds particular [probes](#probes) to the list of readiness probes;
|
||||
- `kubeprobes.WithReadinessProbes(...)` - adds particular [probe functions](#probe-functions) to the list of readiness probes;
|
||||
- `kubeprobes.WithReadinessPath("/some/readiness/path")` - sets readiness probe path to given path (default is `/ready`);
|
||||
- `kubeprobes.WithVerboseOutput()` - enables verbose output by default (returns both failed and passed probes).
|
||||
|
||||
## Probes
|
||||
|
||||
In order to determine the state of particular element of application, probes need to be implemented either by creating [probes from functions](#standard-probes) or by using simple and thread-safe [manual probes](#manual-probes).
|
||||
In order to determine the state of particular element of application, probes need to be implemented either by creating [status determining function](#probe-functions) or by using simple and thread-safe [manual probes](#manual-probes).
|
||||
|
||||
### Standard probes
|
||||
### Probe functions
|
||||
|
||||
Probes (instances of `Probe` interface) are wrappers for functions that performs user defined logic with given interval of updates in order to determine whether the probe should be marked as healthy or not. Those functions should take no arguments and return error (if no error is returned, the probe is considered to be healthy; if error is returned, the probe is considered to be unhealthy). If given interval is less or equal zero, then function is only checked on probe creation and remains in determined state forever.
|
||||
Probe functions (instances of `ProbeFunction` interface) are wrappers for functions that performs user defined logic with given interval of updates in order to determine whether the probe should be marked as healthy or not. Those functions should take no arguments and return error (if no error is returned, the probe is considered to be healthy; if error is returned, the probe is considered to be unhealthy). If given interval is less or equal zero, then function is only checked on probe creation and remains in determined state forever.
|
||||
|
||||
```go
|
||||
someProbe := kubeprobes.NewProbe("live", func() error {
|
||||
someProbe := kubeprobes.NewProbeFunction("live", func() error {
|
||||
// Some logic here
|
||||
if time.Now().Weekday() == time.Wednesday {
|
||||
// Fail only on wednesday!
|
||||
@ -47,7 +45,7 @@ someProbe := kubeprobes.NewProbe("live", func() error {
|
||||
return nil
|
||||
}, 1 * time.Hour)
|
||||
|
||||
someOtherProbe := kubeprobes.NewProbe("ready", func() error {
|
||||
someOtherProbe := kubeprobes.NewProbeFunction("ready", func() error {
|
||||
// Always healthy
|
||||
return nil
|
||||
}, 0) // This probe is checked once
|
||||
@ -61,7 +59,7 @@ kp, _ := kubeprobes.New(
|
||||
|
||||
### Manual probes
|
||||
|
||||
Manual probes (instances of `ManualProbe` interface) are objects that can be marked either as healthy or unhealthy and implement `Probe` for easy integration. Those objects utilize `sync.RMutex` mechanism to ensure thread-safety.
|
||||
Manual probes (instances of `ManualProbe` interface) are objects that can be marked either as healthy or unhealthy and implement `ProbeFunction` for easy integration. Those objects utilize `sync.RMutex` mechanism to ensure thread-safety.
|
||||
|
||||
Those probes can be changed by user with provided methods:
|
||||
|
||||
@ -80,9 +78,7 @@ kp, _ := kubeprobes.New(
|
||||
kubeprobes.WithReadinessProbes(someOtherProbe),
|
||||
)
|
||||
|
||||
// Can be later marked according to needs
|
||||
someProbe.Pass()
|
||||
someOtherProbe.FailWithCause(errors.New("I'm not doing anything!"))
|
||||
// Can be later marked according
|
||||
```
|
||||
|
||||
## Direct handler access
|
||||
@ -109,9 +105,9 @@ appProbe := func() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create manual probes
|
||||
live := kubeprobes.NewManualProbe("liveness")
|
||||
ready := kubeprobes.NewManualProbe("readiness")
|
||||
// Create stateful probes
|
||||
live := kubeprobes.NewStatefulProbe()
|
||||
ready := kubeprobes.NewStatefulProbe()
|
||||
|
||||
// Prepare handler
|
||||
kp, err := kubeprobes.New(
|
||||
|
294
kubeprobes.go
294
kubeprobes.go
@ -1,147 +1,147 @@
|
||||
package kubeprobes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Kubeprobes represents liveness & readiness probes handler.
|
||||
type Kubeprobes interface {
|
||||
http.Handler
|
||||
|
||||
// LivenessHandler returns [http.Handler] for liveness probes.
|
||||
LivenessHandler() http.Handler
|
||||
// ReadinessHandler returns [http.Handler] for readiness probes.
|
||||
ReadinessHandler() http.Handler
|
||||
}
|
||||
|
||||
type kubeprobes struct {
|
||||
livenessProbes []Probe
|
||||
readinessProbes []Probe
|
||||
|
||||
verbose bool
|
||||
|
||||
pathLive string
|
||||
pathReady string
|
||||
}
|
||||
|
||||
// New returns a new instance of a Kubernetes probes with given options.
|
||||
func New(options ...Option) (Kubeprobes, error) {
|
||||
kp := &kubeprobes{
|
||||
livenessProbes: []Probe{},
|
||||
readinessProbes: []Probe{},
|
||||
pathLive: defaultLivenessPath,
|
||||
pathReady: defaultReadinessPath,
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
option.apply(kp)
|
||||
}
|
||||
|
||||
if err := kp.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return kp, nil
|
||||
}
|
||||
|
||||
func (kp *kubeprobes) validate() error {
|
||||
var err error
|
||||
|
||||
if kp.pathLive == "" {
|
||||
err = errors.Join(err, fmt.Errorf("liveness probe path must not be empty"))
|
||||
}
|
||||
|
||||
if kp.pathReady == "" {
|
||||
err = errors.Join(err, fmt.Errorf("readiness probe path must not be empty"))
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(kp.pathLive, "/") {
|
||||
err = errors.Join(err, fmt.Errorf("liveness probe path must start with slash (current: %q)", kp.pathLive))
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(kp.pathReady, "/") {
|
||||
err = errors.Join(err, fmt.Errorf("readiness probe path must start with slash (current: %q)", kp.pathReady))
|
||||
}
|
||||
|
||||
if kp.pathLive == kp.pathReady {
|
||||
err = errors.Join(err, fmt.Errorf("liveness and readiness probes have the same values (both %q)", kp.pathLive))
|
||||
}
|
||||
|
||||
if len(kp.livenessProbes) == 0 {
|
||||
err = errors.Join(err, fmt.Errorf("no liveness probes defined"))
|
||||
}
|
||||
|
||||
if len(kp.readinessProbes) == 0 {
|
||||
err = errors.Join(err, fmt.Errorf("no readiness probes defined"))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
type probesResponse struct {
|
||||
Passed []statusEntry `json:"passed,omitempty"`
|
||||
Failed []statusEntry `json:"failed,omitempty"`
|
||||
}
|
||||
|
||||
func (kp *kubeprobes) handleLiveness(w http.ResponseWriter, r *http.Request) {
|
||||
sq := newStatusQuery(kp.livenessProbes)
|
||||
output := probesResponse{}
|
||||
|
||||
sq.wait()
|
||||
output.Failed = sq.failed
|
||||
if r.URL.Query().Has(verboseOutputFlag) || kp.verbose {
|
||||
output.Passed = sq.passed
|
||||
}
|
||||
|
||||
w.Header().Add(headerContentType, contentTypeJSON)
|
||||
if sq.ok {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(output)
|
||||
}
|
||||
|
||||
func (kp *kubeprobes) handleReadiness(w http.ResponseWriter, r *http.Request) {
|
||||
sq := newStatusQuery(append(kp.livenessProbes, kp.readinessProbes...))
|
||||
output := probesResponse{}
|
||||
|
||||
sq.wait()
|
||||
output.Failed = sq.failed
|
||||
if r.URL.Query().Has(verboseOutputFlag) || kp.verbose {
|
||||
output.Passed = sq.passed
|
||||
}
|
||||
|
||||
w.Header().Add(headerContentType, contentTypeJSON)
|
||||
if sq.ok {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(output)
|
||||
}
|
||||
|
||||
// LivenessHandler implements Kubeprobes.
|
||||
func (kp *kubeprobes) LivenessHandler() http.Handler {
|
||||
return http.HandlerFunc(kp.handleLiveness)
|
||||
}
|
||||
|
||||
// ReadinessHandler implements Kubeprobes.
|
||||
func (kp *kubeprobes) ReadinessHandler() http.Handler {
|
||||
return http.HandlerFunc(kp.handleReadiness)
|
||||
}
|
||||
|
||||
// ServeHTTP implements Kubeprobes.
|
||||
func (kp *kubeprobes) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case kp.pathLive:
|
||||
kp.handleLiveness(w, r)
|
||||
case kp.pathReady:
|
||||
kp.handleReadiness(w, r)
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
package kubeprobes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Kubeprobes represents liveness & readiness probes handler.
|
||||
type Kubeprobes interface {
|
||||
http.Handler
|
||||
|
||||
// LivenessHandler returns [http.Handler] for liveness probes.
|
||||
LivenessHandler() http.Handler
|
||||
// ReadinessHandler returns [http.Handler] for readiness probes.
|
||||
ReadinessHandler() http.Handler
|
||||
}
|
||||
|
||||
type kubeprobes struct {
|
||||
livenessProbes []ProbeFunction
|
||||
readinessProbes []ProbeFunction
|
||||
|
||||
verbose bool
|
||||
|
||||
pathLive string
|
||||
pathReady string
|
||||
}
|
||||
|
||||
// New returns a new instance of a Kubernetes probes with given options.
|
||||
func New(options ...Option) (Kubeprobes, error) {
|
||||
kp := &kubeprobes{
|
||||
livenessProbes: []ProbeFunction{},
|
||||
readinessProbes: []ProbeFunction{},
|
||||
pathLive: defaultLivenessPath,
|
||||
pathReady: defaultReadinessPath,
|
||||
}
|
||||
|
||||
for _, option := range options {
|
||||
option.apply(kp)
|
||||
}
|
||||
|
||||
if err := kp.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return kp, nil
|
||||
}
|
||||
|
||||
func (kp *kubeprobes) validate() error {
|
||||
var err error
|
||||
|
||||
if kp.pathLive == "" {
|
||||
err = errors.Join(err, fmt.Errorf("liveness probe path must not be empty"))
|
||||
}
|
||||
|
||||
if kp.pathReady == "" {
|
||||
err = errors.Join(err, fmt.Errorf("readiness probe path must not be empty"))
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(kp.pathLive, "/") {
|
||||
err = errors.Join(err, fmt.Errorf("liveness probe path must start with slash (current: %q)", kp.pathLive))
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(kp.pathReady, "/") {
|
||||
err = errors.Join(err, fmt.Errorf("readiness probe path must start with slash (current: %q)", kp.pathReady))
|
||||
}
|
||||
|
||||
if kp.pathLive == kp.pathReady {
|
||||
err = errors.Join(err, fmt.Errorf("liveness and readiness probes have the same values (both %q)", kp.pathLive))
|
||||
}
|
||||
|
||||
if len(kp.livenessProbes) == 0 {
|
||||
err = errors.Join(err, fmt.Errorf("no liveness probes defined"))
|
||||
}
|
||||
|
||||
if len(kp.readinessProbes) == 0 {
|
||||
err = errors.Join(err, fmt.Errorf("no readiness probes defined"))
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
type probesResponse struct {
|
||||
Passed []statusEntry `json:"passed,omitempty"`
|
||||
Failed []statusEntry `json:"failed,omitempty"`
|
||||
}
|
||||
|
||||
func (kp *kubeprobes) handleLiveness(w http.ResponseWriter, r *http.Request) {
|
||||
sq := newStatusQuery(kp.livenessProbes)
|
||||
output := probesResponse{}
|
||||
|
||||
sq.wait()
|
||||
output.Failed = sq.failed
|
||||
if r.URL.Query().Has(verboseOutputFlag) || kp.verbose {
|
||||
output.Passed = sq.passed
|
||||
}
|
||||
|
||||
w.Header().Add(headerContentType, contentTypeJSON)
|
||||
if sq.ok {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(output)
|
||||
}
|
||||
|
||||
func (kp *kubeprobes) handleReadiness(w http.ResponseWriter, r *http.Request) {
|
||||
sq := newStatusQuery(append(kp.livenessProbes, kp.readinessProbes...))
|
||||
output := probesResponse{}
|
||||
|
||||
sq.wait()
|
||||
output.Failed = sq.failed
|
||||
if r.URL.Query().Has(verboseOutputFlag) || kp.verbose {
|
||||
output.Passed = sq.passed
|
||||
}
|
||||
|
||||
w.Header().Add(headerContentType, contentTypeJSON)
|
||||
if sq.ok {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(output)
|
||||
}
|
||||
|
||||
// LivenessHandler implements Kubeprobes.
|
||||
func (kp *kubeprobes) LivenessHandler() http.Handler {
|
||||
return http.HandlerFunc(kp.handleLiveness)
|
||||
}
|
||||
|
||||
// ReadinessHandler implements Kubeprobes.
|
||||
func (kp *kubeprobes) ReadinessHandler() http.Handler {
|
||||
return http.HandlerFunc(kp.handleReadiness)
|
||||
}
|
||||
|
||||
// ServeHTTP implements Kubeprobes.
|
||||
func (kp *kubeprobes) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case kp.pathLive:
|
||||
kp.handleLiveness(w, r)
|
||||
case kp.pathReady:
|
||||
kp.handleReadiness(w, r)
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ func (o option) apply(kp *kubeprobes) {
|
||||
}
|
||||
|
||||
// WithLivenessProbes adds given probe functions to the set of liveness probes.
|
||||
func WithLivenessProbes(probes ...Probe) Option {
|
||||
func WithLivenessProbes(probes ...ProbeFunction) Option {
|
||||
return option(func(kp *kubeprobes) {
|
||||
kp.livenessProbes = append(kp.livenessProbes, probes...)
|
||||
})
|
||||
@ -26,7 +26,7 @@ func WithLivenessPath(path string) Option {
|
||||
}
|
||||
|
||||
// WithReadinessProbes adds given probe functions to the set of readiness probes.
|
||||
func WithReadinessProbes(probes ...Probe) Option {
|
||||
func WithReadinessProbes(probes ...ProbeFunction) Option {
|
||||
return option(func(kp *kubeprobes) {
|
||||
kp.readinessProbes = append(kp.readinessProbes, probes...)
|
||||
})
|
||||
|
@ -1,182 +1,182 @@
|
||||
package kubeprobes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func getStatusFromEndpoint(t *testing.T, client *http.Client, endpoint string) int {
|
||||
t.Helper()
|
||||
resp, err := client.Get(endpoint)
|
||||
if err != nil {
|
||||
t.Errorf("error getting status from endpoint: %s", err)
|
||||
}
|
||||
return resp.StatusCode
|
||||
}
|
||||
|
||||
func TestValidation(t *testing.T) {
|
||||
var (
|
||||
live, _ = NewManualProbe("live")
|
||||
ready, _ = NewManualProbe("ready")
|
||||
)
|
||||
|
||||
tests := map[string]struct {
|
||||
opts []Option
|
||||
expectedError bool
|
||||
}{
|
||||
"no modifications and no error": {
|
||||
opts: []Option{
|
||||
WithLivenessProbes(live),
|
||||
WithReadinessProbes(ready),
|
||||
},
|
||||
},
|
||||
"modifications and no error": {
|
||||
opts: []Option{
|
||||
WithLivenessProbes(live),
|
||||
WithReadinessProbes(ready),
|
||||
WithLivenessPath("/livez"),
|
||||
WithReadinessPath("/readyz"),
|
||||
},
|
||||
},
|
||||
"missing liveness probes": {
|
||||
opts: []Option{
|
||||
WithReadinessProbes(ready),
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
"missing readiness probes": {
|
||||
opts: []Option{
|
||||
WithLivenessProbes(live),
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
"liveness probe path empty": {
|
||||
opts: []Option{
|
||||
WithLivenessProbes(live),
|
||||
WithReadinessProbes(ready),
|
||||
WithLivenessPath(""),
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
"readiness probe path empty": {
|
||||
opts: []Option{
|
||||
WithLivenessProbes(live),
|
||||
WithReadinessProbes(ready),
|
||||
WithReadinessPath(""),
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
"liveness probe path does not start with slash": {
|
||||
opts: []Option{
|
||||
WithLivenessProbes(live),
|
||||
WithReadinessProbes(ready),
|
||||
WithLivenessPath("livez"),
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
"readiness probe path does not start with slash": {
|
||||
opts: []Option{
|
||||
WithLivenessProbes(live),
|
||||
WithReadinessProbes(ready),
|
||||
WithReadinessPath("readyz"),
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
"liveness and readiness probe paths are equal": {
|
||||
opts: []Option{
|
||||
WithLivenessProbes(live),
|
||||
WithReadinessProbes(ready),
|
||||
WithLivenessPath("/check"),
|
||||
WithReadinessPath("/check"),
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
name, tc := name, tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
_, err := New(tc.opts...)
|
||||
switch {
|
||||
case err == nil && tc.expectedError:
|
||||
t.Error("expected error, but no error was returned")
|
||||
case err != nil && !tc.expectedError:
|
||||
t.Errorf("expected no error but got %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandler(t *testing.T) {
|
||||
var (
|
||||
live, _ = NewManualProbe("live")
|
||||
ready, _ = NewManualProbe("ready")
|
||||
)
|
||||
|
||||
tests := map[string]struct {
|
||||
livenessProbeTransformation func(*testing.T, ManualProbe)
|
||||
readinessProbeTransformation func(*testing.T, ManualProbe)
|
||||
expectedLiveStatus int
|
||||
expectedReadyStatus int
|
||||
}{
|
||||
"not live": {
|
||||
livenessProbeTransformation: markAsDown,
|
||||
readinessProbeTransformation: markAsDown,
|
||||
expectedLiveStatus: http.StatusServiceUnavailable,
|
||||
expectedReadyStatus: http.StatusServiceUnavailable,
|
||||
},
|
||||
"live but not ready": {
|
||||
livenessProbeTransformation: markAsUp,
|
||||
readinessProbeTransformation: markAsDown,
|
||||
expectedLiveStatus: http.StatusOK,
|
||||
expectedReadyStatus: http.StatusServiceUnavailable,
|
||||
},
|
||||
"live and ready": {
|
||||
livenessProbeTransformation: markAsUp,
|
||||
readinessProbeTransformation: markAsUp,
|
||||
expectedLiveStatus: http.StatusOK,
|
||||
expectedReadyStatus: http.StatusOK,
|
||||
},
|
||||
"ready but not live - should never happen": {
|
||||
livenessProbeTransformation: markAsDown,
|
||||
readinessProbeTransformation: markAsUp,
|
||||
expectedLiveStatus: http.StatusServiceUnavailable,
|
||||
expectedReadyStatus: http.StatusServiceUnavailable,
|
||||
},
|
||||
}
|
||||
|
||||
kp, err := New(
|
||||
WithLivenessProbes(live),
|
||||
WithReadinessProbes(ready),
|
||||
)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
srv := httptest.NewServer(kp)
|
||||
defer srv.Close()
|
||||
client := srv.Client()
|
||||
|
||||
for name, test := range tests {
|
||||
name, test := name, test
|
||||
t.Run(name, func(t *testing.T) {
|
||||
test.livenessProbeTransformation(t, live)
|
||||
test.readinessProbeTransformation(t, ready)
|
||||
|
||||
liveStatus := getStatusFromEndpoint(t, client, srv.URL+defaultLivenessPath)
|
||||
readyStatus := getStatusFromEndpoint(t, client, srv.URL+defaultReadinessPath)
|
||||
otherStatus := getStatusFromEndpoint(t, client, srv.URL+"/something")
|
||||
|
||||
if liveStatus != test.expectedLiveStatus {
|
||||
t.Errorf("expected live status %d, got %d", test.expectedLiveStatus, liveStatus)
|
||||
}
|
||||
if readyStatus != test.expectedReadyStatus {
|
||||
t.Errorf("expected ready status %d, got %d", test.expectedReadyStatus, readyStatus)
|
||||
}
|
||||
if otherStatus != http.StatusNotFound {
|
||||
t.Errorf("expected 404 status, got %d", otherStatus)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
package kubeprobes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func getStatusFromEndpoint(t *testing.T, client *http.Client, endpoint string) int {
|
||||
t.Helper()
|
||||
resp, err := client.Get(endpoint)
|
||||
if err != nil {
|
||||
t.Errorf("error getting status from endpoint: %s", err)
|
||||
}
|
||||
return resp.StatusCode
|
||||
}
|
||||
|
||||
func TestValidation(t *testing.T) {
|
||||
var (
|
||||
live, _ = NewManualProbe("live")
|
||||
ready, _ = NewManualProbe("ready")
|
||||
)
|
||||
|
||||
tests := map[string]struct {
|
||||
opts []Option
|
||||
expectedError bool
|
||||
}{
|
||||
"no modifications and no error": {
|
||||
opts: []Option{
|
||||
WithLivenessProbes(live),
|
||||
WithReadinessProbes(ready),
|
||||
},
|
||||
},
|
||||
"modifications and no error": {
|
||||
opts: []Option{
|
||||
WithLivenessProbes(live),
|
||||
WithReadinessProbes(ready),
|
||||
WithLivenessPath("/livez"),
|
||||
WithReadinessPath("/readyz"),
|
||||
},
|
||||
},
|
||||
"missing liveness probes": {
|
||||
opts: []Option{
|
||||
WithReadinessProbes(ready),
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
"missing readiness probes": {
|
||||
opts: []Option{
|
||||
WithLivenessProbes(live),
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
"liveness probe path empty": {
|
||||
opts: []Option{
|
||||
WithLivenessProbes(live),
|
||||
WithReadinessProbes(ready),
|
||||
WithLivenessPath(""),
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
"readiness probe path empty": {
|
||||
opts: []Option{
|
||||
WithLivenessProbes(live),
|
||||
WithReadinessProbes(ready),
|
||||
WithReadinessPath(""),
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
"liveness probe path does not start with slash": {
|
||||
opts: []Option{
|
||||
WithLivenessProbes(live),
|
||||
WithReadinessProbes(ready),
|
||||
WithLivenessPath("livez"),
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
"readiness probe path does not start with slash": {
|
||||
opts: []Option{
|
||||
WithLivenessProbes(live),
|
||||
WithReadinessProbes(ready),
|
||||
WithReadinessPath("readyz"),
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
"liveness and readiness probe paths are equal": {
|
||||
opts: []Option{
|
||||
WithLivenessProbes(live),
|
||||
WithReadinessProbes(ready),
|
||||
WithLivenessPath("/check"),
|
||||
WithReadinessPath("/check"),
|
||||
},
|
||||
expectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range tests {
|
||||
name, tc := name, tc
|
||||
t.Run(name, func(t *testing.T) {
|
||||
_, err := New(tc.opts...)
|
||||
switch {
|
||||
case err == nil && tc.expectedError:
|
||||
t.Error("expected error, but no error was returned")
|
||||
case err != nil && !tc.expectedError:
|
||||
t.Errorf("expected no error but got %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandler(t *testing.T) {
|
||||
var (
|
||||
live, _ = NewManualProbe("live")
|
||||
ready, _ = NewManualProbe("ready")
|
||||
)
|
||||
|
||||
tests := map[string]struct {
|
||||
livenessProbeTransformation func(*testing.T, ManualProbe)
|
||||
readinessProbeTransformation func(*testing.T, ManualProbe)
|
||||
expectedLiveStatus int
|
||||
expectedReadyStatus int
|
||||
}{
|
||||
"not live": {
|
||||
livenessProbeTransformation: markAsDown,
|
||||
readinessProbeTransformation: markAsDown,
|
||||
expectedLiveStatus: http.StatusServiceUnavailable,
|
||||
expectedReadyStatus: http.StatusServiceUnavailable,
|
||||
},
|
||||
"live but not ready": {
|
||||
livenessProbeTransformation: markAsUp,
|
||||
readinessProbeTransformation: markAsDown,
|
||||
expectedLiveStatus: http.StatusOK,
|
||||
expectedReadyStatus: http.StatusServiceUnavailable,
|
||||
},
|
||||
"live and ready": {
|
||||
livenessProbeTransformation: markAsUp,
|
||||
readinessProbeTransformation: markAsUp,
|
||||
expectedLiveStatus: http.StatusOK,
|
||||
expectedReadyStatus: http.StatusOK,
|
||||
},
|
||||
"ready but not live - should never happen": {
|
||||
livenessProbeTransformation: markAsDown,
|
||||
readinessProbeTransformation: markAsUp,
|
||||
expectedLiveStatus: http.StatusServiceUnavailable,
|
||||
expectedReadyStatus: http.StatusServiceUnavailable,
|
||||
},
|
||||
}
|
||||
|
||||
kp, err := New(
|
||||
WithLivenessProbes(live),
|
||||
WithReadinessProbes(ready),
|
||||
)
|
||||
if err != nil {
|
||||
t.Errorf("expected no error, got %v", err)
|
||||
}
|
||||
|
||||
srv := httptest.NewServer(kp)
|
||||
defer srv.Close()
|
||||
client := srv.Client()
|
||||
|
||||
for name, test := range tests {
|
||||
name, test := name, test
|
||||
t.Run(name, func(t *testing.T) {
|
||||
test.livenessProbeTransformation(t, live)
|
||||
test.readinessProbeTransformation(t, ready)
|
||||
|
||||
liveStatus := getStatusFromEndpoint(t, client, srv.URL+defaultLivenessPath)
|
||||
readyStatus := getStatusFromEndpoint(t, client, srv.URL+defaultReadinessPath)
|
||||
otherStatus := getStatusFromEndpoint(t, client, srv.URL+"/something")
|
||||
|
||||
if liveStatus != test.expectedLiveStatus {
|
||||
t.Errorf("expected live status %d, got %d", test.expectedLiveStatus, liveStatus)
|
||||
}
|
||||
if readyStatus != test.expectedReadyStatus {
|
||||
t.Errorf("expected ready status %d, got %d", test.expectedReadyStatus, readyStatus)
|
||||
}
|
||||
if otherStatus != http.StatusNotFound {
|
||||
t.Errorf("expected 404 status, got %d", otherStatus)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
2805
package-lock.json
generated
2805
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,17 +1,17 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "kubeprobes",
|
||||
"version": "1.3.1-rc.11",
|
||||
"version": "1.3.0",
|
||||
"scripts": {
|
||||
"release": "./node_modules/.bin/semantic-release"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@saithodev/semantic-release-gitea": "^2.1.0",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/commit-analyzer": "^12.0.0",
|
||||
"@semantic-release/commit-analyzer": "^11.0.0",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@semantic-release/npm": "^12.0.0",
|
||||
"@semantic-release/release-notes-generator": "^13.0.0",
|
||||
"@semantic-release/npm": "^11.0.1",
|
||||
"@semantic-release/release-notes-generator": "^12.0.1",
|
||||
"conventional-changelog-conventionalcommits": "^7.0.2",
|
||||
"semantic-release": "^23.0.0"
|
||||
}
|
||||
|
@ -5,15 +5,15 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Probe is a wrapper for a function that determines whether
|
||||
// ProbeFunction is a wrapper for a function that determines whether
|
||||
// the given metric may be marked as correctly functioning.
|
||||
// It not, the error should be returned.
|
||||
type Probe interface {
|
||||
type ProbeFunction interface {
|
||||
name() string
|
||||
status() error
|
||||
}
|
||||
|
||||
type probe struct {
|
||||
type probeFunction struct {
|
||||
probeName string
|
||||
probeFunc func() error
|
||||
refreshInterval time.Duration
|
||||
@ -22,20 +22,20 @@ type probe struct {
|
||||
err error
|
||||
}
|
||||
|
||||
// NewProbe returns new instance of [Probe].
|
||||
// NewProbeFunction returns new instance of [ProbeFunction].
|
||||
//
|
||||
// If update interval is less or equal zero then probe is updated only
|
||||
// on its creation and remains in the same state forever.
|
||||
func NewProbe(
|
||||
func NewProbeFunction(
|
||||
name string,
|
||||
fn func() error,
|
||||
updateInterval time.Duration,
|
||||
) (Probe, error) {
|
||||
) (ProbeFunction, error) {
|
||||
if name == "" {
|
||||
return nil, errProbeNameEmpty
|
||||
}
|
||||
|
||||
pf := &probe{
|
||||
pf := &probeFunction{
|
||||
probeName: name,
|
||||
probeFunc: fn,
|
||||
refreshInterval: updateInterval,
|
||||
@ -47,25 +47,25 @@ func NewProbe(
|
||||
}
|
||||
|
||||
// name implements ProbeFunction.
|
||||
func (pf *probe) name() string {
|
||||
func (pf *probeFunction) name() string {
|
||||
return pf.probeName
|
||||
}
|
||||
|
||||
// status implements ProbeFunction.
|
||||
func (pf *probe) status() error {
|
||||
func (pf *probeFunction) status() error {
|
||||
pf.mux.RLock()
|
||||
defer pf.mux.RUnlock()
|
||||
return pf.err
|
||||
}
|
||||
|
||||
func (pf *probe) update() {
|
||||
func (pf *probeFunction) update() {
|
||||
err := pf.probeFunc()
|
||||
pf.mux.Lock()
|
||||
pf.err = err
|
||||
pf.mux.Unlock()
|
||||
}
|
||||
|
||||
func (pf *probe) autoUpdate() {
|
||||
func (pf *probeFunction) autoUpdate() {
|
||||
pf.update()
|
||||
if pf.refreshInterval <= 0 {
|
||||
return
|
@ -7,7 +7,7 @@ import (
|
||||
// ManualProbe represents the simple probe that can be either
|
||||
// marked as "up" (healthy) or "down" (unhealthy).
|
||||
type ManualProbe interface {
|
||||
Probe
|
||||
ProbeFunction
|
||||
|
||||
// Pass marks the probe as healthy.
|
||||
Pass()
|
||||
|
2
query.go
2
query.go
@ -20,7 +20,7 @@ func (sq *statusQuery) wait() {
|
||||
sq.wg.Wait()
|
||||
}
|
||||
|
||||
func newStatusQuery(probes []Probe) *statusQuery {
|
||||
func newStatusQuery(probes []ProbeFunction) *statusQuery {
|
||||
sq := &statusQuery{
|
||||
ok: true,
|
||||
passed: make([]statusEntry, 0, len(probes)),
|
||||
|
@ -6,28 +6,28 @@ import (
|
||||
|
||||
func TestStatusQueryIsAllGreen(t *testing.T) {
|
||||
var (
|
||||
probePassing, _ = NewProbe("pass", func() error {
|
||||
probePassing, _ = NewProbeFunction("pass", func() error {
|
||||
return nil
|
||||
}, 0)
|
||||
probeFailing, _ = NewProbe("fail", func() error {
|
||||
probeFailing, _ = NewProbeFunction("fail", func() error {
|
||||
return errProbeFailed
|
||||
}, 0)
|
||||
)
|
||||
|
||||
tests := map[string]struct {
|
||||
probes []Probe
|
||||
probes []ProbeFunction
|
||||
expectedStatus bool
|
||||
}{
|
||||
"all green": {
|
||||
probes: []Probe{probePassing},
|
||||
probes: []ProbeFunction{probePassing},
|
||||
expectedStatus: true,
|
||||
},
|
||||
"some failed": {
|
||||
probes: []Probe{probePassing, probeFailing},
|
||||
probes: []ProbeFunction{probePassing, probeFailing},
|
||||
expectedStatus: false,
|
||||
},
|
||||
"all failed": {
|
||||
probes: []Probe{probeFailing},
|
||||
probes: []ProbeFunction{probeFailing},
|
||||
expectedStatus: false,
|
||||
},
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user