feat: add healthcheck capabilities

This commit is contained in:
Alexis Couvreur
2022-11-14 18:46:38 +00:00
parent 80f2304375
commit 2e0dc8320d
6 changed files with 121 additions and 0 deletions

View File

@@ -29,6 +29,9 @@ Which allows you to start your containers on demand and shut them down automatic
- [Creating your own loading theme](#creating-your-own-loading-theme)
- [Blocking the loading until the session is ready](#blocking-the-loading-until-the-session-is-ready)
- [Saving the state to a file](#saving-the-state-to-a-file)
- [Sablier Healthcheck](#sablier-healthcheck)
- [Using the `/health` route](#using-the-health-route)
- [Using the `sablier health` command](#using-the-sablier-health-command)
- [Glossary](#glossary)
- [Versioning](#versioning)
- [Credits](#credits)
@@ -294,7 +297,29 @@ If the file doesn't exist it will be created, and it will be syned upon exit.
Loaded instances that expired during the restart won't be changed though, they will simply be ignored.
## Sablier Healthcheck
### Using the `/health` route
You can use the route `/health` to check for healthiness.
- Returns 200 `OK` when ready
- Returns 503 `Service Unavailable` when terminating
### Using the `sablier health` command
You can use the command `sablier health` to check for healthiness.
`sablier health` takes on argument `--url` which defaults to `http://localhost:10000/health`.
```yml
services:
sablier:
image: acouvreur/sablier:1.2.0
healthcheck:
test: ["sablier", "health"]
interval: 1m30s
```
## Glossary

View File

@@ -0,0 +1,31 @@
package healthcheck
import (
"io"
"net/http"
)
const (
healthy = true
unhealthy = false
)
func Health(url string) (string, bool) {
resp, err := http.Get(url)
if err != nil {
return err.Error(), unhealthy
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return err.Error(), unhealthy
}
if resp.StatusCode >= 400 {
return string(body), unhealthy
}
return string(body), healthy
}

33
app/http/routes/health.go Normal file
View File

@@ -0,0 +1,33 @@
package routes
import (
"context"
"net/http"
"github.com/gin-gonic/gin"
)
type Health struct {
TerminatingStatusCode int `description:"Terminating status code" json:"terminatingStatusCode,omitempty" yaml:"terminatingStatusCode,omitempty" export:"true"`
terminating bool
}
func (h *Health) SetDefaults() {
h.TerminatingStatusCode = http.StatusServiceUnavailable
}
func (h *Health) WithContext(ctx context.Context) {
go func() {
<-ctx.Done()
h.terminating = true
}()
}
func (h *Health) ServeHTTP(c *gin.Context) {
statusCode := http.StatusOK
if h.terminating {
statusCode = h.TerminatingStatusCode
}
c.String(statusCode, http.StatusText(statusCode))
}

View File

@@ -34,6 +34,10 @@ func Start(serverConf config.Server, strategyConf config.Strategy, sessionManage
api.GET("/strategies/dynamic/themes", strategy.ServeDynamicThemes)
api.GET("/strategies/blocking", strategy.ServeBlocking)
}
health := routes.Health{}
health.SetDefaults()
health.WithContext(ctx)
base.GET("/health", health.ServeHTTP)
}
srv := &http.Server{

25
cmd/health.go Normal file
View File

@@ -0,0 +1,25 @@
package cmd
import (
"fmt"
"os"
"github.com/acouvreur/sablier/app/http/healthcheck"
"github.com/spf13/cobra"
)
var healthCmd = &cobra.Command{
Use: "health",
Short: "Calls the health endpoint of a Sablier instance",
Run: func(cmd *cobra.Command, args []string) {
details, healthy := healthcheck.Health(cmd.Flag("url").Value.String())
if healthy {
fmt.Fprintf(os.Stderr, "healthy: %v\n", details)
os.Exit(0)
} else {
fmt.Fprintf(os.Stderr, "unhealthy: %v\n", details)
os.Exit(1)
}
},
}

View File

@@ -79,6 +79,9 @@ It provides an integrations with multiple reverse proxies and different loading
rootCmd.AddCommand(startCmd)
rootCmd.AddCommand(versionCmd)
healthCmd.Flags().String("url", "http://localhost:10000/health", "Sablier health endpoint")
rootCmd.AddCommand(healthCmd)
return rootCmd
}