mirror of
https://github.com/axllent/mailpit.git
synced 2025-08-15 20:13:16 +02:00
Feature: Add readyz subcommand for Docker healthcheck (#270)
This commit is contained in:
@@ -25,4 +25,6 @@ RUN apk add --no-cache tzdata
|
|||||||
|
|
||||||
EXPOSE 1025/tcp 1110/tcp 8025/tcp
|
EXPOSE 1025/tcp 1110/tcp 8025/tcp
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=15s CMD /mailpit readyz
|
||||||
|
|
||||||
ENTRYPOINT ["/mailpit"]
|
ENTRYPOINT ["/mailpit"]
|
||||||
|
75
cmd/readyz.go
Normal file
75
cmd/readyz.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/axllent/mailpit/config"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
useHTTPS bool
|
||||||
|
)
|
||||||
|
|
||||||
|
// readyzCmd represents the healthcheck command
|
||||||
|
var readyzCmd = &cobra.Command{
|
||||||
|
Use: "readyz",
|
||||||
|
Short: "Run a healthcheck to test if Mailpit is running",
|
||||||
|
Long: `This command connects to the /readyz endpoint of a running Mailpit server
|
||||||
|
and exits with a status of 0 if the connection is successful, else with a
|
||||||
|
status 1 if unhealthy.
|
||||||
|
|
||||||
|
If running within Docker, it should automatically detect environment
|
||||||
|
settings to determine the HTTP bind interface & port.
|
||||||
|
`,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
webroot := strings.TrimRight(path.Join("/", config.Webroot, "/"), "/") + "/"
|
||||||
|
proto := "http"
|
||||||
|
if useHTTPS {
|
||||||
|
proto = "https"
|
||||||
|
}
|
||||||
|
|
||||||
|
uri := fmt.Sprintf("%s://%s%sreadyz", proto, config.HTTPListen, webroot)
|
||||||
|
|
||||||
|
conf := &http.Transport{
|
||||||
|
IdleConnTimeout: time.Second * 5,
|
||||||
|
ExpectContinueTimeout: time.Second * 5,
|
||||||
|
TLSHandshakeTimeout: time.Second * 5,
|
||||||
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||||
|
}
|
||||||
|
client := &http.Client{Transport: conf}
|
||||||
|
|
||||||
|
res, err := client.Get(uri)
|
||||||
|
if err != nil || res.StatusCode != 200 {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(readyzCmd)
|
||||||
|
|
||||||
|
if len(os.Getenv("MP_UI_BIND_ADDR")) > 0 {
|
||||||
|
config.HTTPListen = os.Getenv("MP_UI_BIND_ADDR")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(os.Getenv("MP_WEBROOT")) > 0 {
|
||||||
|
config.Webroot = os.Getenv("MP_WEBROOT")
|
||||||
|
}
|
||||||
|
|
||||||
|
config.UITLSCert = os.Getenv("MP_UI_TLS_CERT")
|
||||||
|
|
||||||
|
if config.UITLSCert != "" {
|
||||||
|
useHTTPS = true
|
||||||
|
}
|
||||||
|
|
||||||
|
readyzCmd.Flags().StringVarP(&config.HTTPListen, "listen", "l", config.HTTPListen, "Set the HTTP bind interface & port")
|
||||||
|
readyzCmd.Flags().StringVar(&config.Webroot, "webroot", config.Webroot, "Set the webroot for web UI & API")
|
||||||
|
readyzCmd.Flags().BoolVar(&useHTTPS, "https", useHTTPS, "Connect via HTTPS (ignores HTTPS validation)")
|
||||||
|
}
|
@@ -89,7 +89,7 @@ func init() {
|
|||||||
rootCmd.Flags().BoolVarP(&logger.VerboseLogging, "verbose", "v", logger.VerboseLogging, "Verbose logging")
|
rootCmd.Flags().BoolVarP(&logger.VerboseLogging, "verbose", "v", logger.VerboseLogging, "Verbose logging")
|
||||||
|
|
||||||
// Web UI / API
|
// Web UI / API
|
||||||
rootCmd.Flags().StringVarP(&config.HTTPListen, "listen", "l", config.HTTPListen, "HTTP bind interface and port for UI")
|
rootCmd.Flags().StringVarP(&config.HTTPListen, "listen", "l", config.HTTPListen, "HTTP bind interface & port for UI")
|
||||||
rootCmd.Flags().StringVar(&config.Webroot, "webroot", config.Webroot, "Set the webroot for web UI & API")
|
rootCmd.Flags().StringVar(&config.Webroot, "webroot", config.Webroot, "Set the webroot for web UI & API")
|
||||||
rootCmd.Flags().StringVar(&config.UIAuthFile, "ui-auth-file", config.UIAuthFile, "A password file for web UI & API authentication")
|
rootCmd.Flags().StringVar(&config.UIAuthFile, "ui-auth-file", config.UIAuthFile, "A password file for web UI & API authentication")
|
||||||
rootCmd.Flags().StringVar(&config.UITLSCert, "ui-tls-cert", config.UITLSCert, "TLS certificate for web UI (HTTPS) - requires ui-tls-key")
|
rootCmd.Flags().StringVar(&config.UITLSCert, "ui-tls-cert", config.UITLSCert, "TLS certificate for web UI (HTTPS) - requires ui-tls-key")
|
||||||
|
@@ -115,6 +115,11 @@ func Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ping the database connection and return an error if unsuccessful
|
||||||
|
func Ping() error {
|
||||||
|
return db.Ping()
|
||||||
|
}
|
||||||
|
|
||||||
// StatsGet returns the total/unread statistics for a mailbox
|
// StatsGet returns the total/unread statistics for a mailbox
|
||||||
func StatsGet() MailboxStats {
|
func StatsGet() MailboxStats {
|
||||||
var (
|
var (
|
||||||
|
@@ -3,12 +3,14 @@ package handlers
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/axllent/mailpit/internal/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReadyzHandler is a ready probe that signals k8s to be able to retrieve traffic
|
// ReadyzHandler is a ready probe that signals k8s to be able to retrieve traffic
|
||||||
func ReadyzHandler(isReady *atomic.Value) http.HandlerFunc {
|
func ReadyzHandler(isReady *atomic.Value) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, _ *http.Request) {
|
return func(w http.ResponseWriter, _ *http.Request) {
|
||||||
if isReady == nil || !isReady.Load().(bool) {
|
if isReady == nil || !isReady.Load().(bool) || storage.Ping() != nil {
|
||||||
http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
|
http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user