Skip to content

Commit

Permalink
feat(nofify): Add support for Healthchecks notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
gabe565 committed Mar 4, 2024
1 parent 4ecd2ca commit 9739d06
Show file tree
Hide file tree
Showing 16 changed files with 244 additions and 59 deletions.
20 changes: 20 additions & 0 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/clevyr/kubedb/cmd/status"
"github.com/clevyr/kubedb/internal/config/flags"
"github.com/clevyr/kubedb/internal/consts"
"github.com/clevyr/kubedb/internal/notifier"
"github.com/clevyr/kubedb/internal/util"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
Expand All @@ -38,6 +39,7 @@ func NewCommand() *cobra.Command {
flags.Pod(cmd)
flags.LogLevel(cmd)
flags.LogFormat(cmd)
flags.Healthchecks(cmd)
flags.Redact(cmd)
cmd.InitDefaultVersionFlag()

Expand Down Expand Up @@ -68,6 +70,7 @@ func preRun(cmd *cobra.Command, args []string) error {
flags.BindLogLevel(cmd)
flags.BindLogFormat(cmd)
flags.BindRedact(cmd)
flags.BindHealthchecks(cmd)

ctx, cancel := signal.NotifyContext(cmd.Context(), os.Interrupt, os.Kill, syscall.SIGTERM)
cmd.PersistentPostRun = func(cmd *cobra.Command, args []string) { cancel() }
Expand All @@ -88,6 +91,23 @@ func preRun(cmd *cobra.Command, args []string) error {
return err
}
initLog(cmd)

if url := viper.GetString(consts.HealthchecksPingUrlKey); url != "" {
if handler, err := notifier.NewHealthchecks(url); err != nil {
log.WithError(err).Error("Notifications creation failed")
} else {
if err := handler.Started(); err != nil {
log.WithError(err).Error("Notifications ping start failed")
}

OnFinalize(func(err error) {
if err := handler.Finished(err); err != nil {
log.WithError(err).Error("Notifications ping finished failed")
}
})
}
}

return nil
}

Expand Down
13 changes: 13 additions & 0 deletions cmd/finalizer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cmd

var finalizers []func(err error)

func OnFinalize(y ...func(err error)) {
finalizers = append(finalizers, y...)
}

func PostRun(err error) {
for _, x := range finalizers {
x(err)
}
}
19 changes: 10 additions & 9 deletions docs/kubedb.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ Painlessly work with databases in Kubernetes.
### Options

```
--context string Kubernetes context name
--dialect string Database dialect. One of (postgres|mariadb|mongodb) (default discovered)
-h, --help help for kubedb
--kubeconfig string Paths to the kubeconfig file (default "$HOME/.kube/config")
--log-format string Log formatter. One of (text|json) (default "text")
--log-level string Log level. One of (trace|debug|info|warning|error|fatal|panic) (default "info")
-n, --namespace string Kubernetes namespace
--pod string Perform detection from a pod instead of searching the namespace
-v, --version version for kubedb
--context string Kubernetes context name
--dialect string Database dialect. One of (postgres|mariadb|mongodb) (default discovered)
--healthchecks-ping-url string Notification handler URL
-h, --help help for kubedb
--kubeconfig string Paths to the kubeconfig file (default "$HOME/.kube/config")
--log-format string Log formatter. One of (text|json) (default "text")
--log-level string Log level. One of (trace|debug|info|warning|error|fatal|panic) (default "info")
-n, --namespace string Kubernetes namespace
--pod string Perform detection from a pod instead of searching the namespace
-v, --version version for kubedb
```

### SEE ALSO
Expand Down
15 changes: 8 additions & 7 deletions docs/kubedb_dump.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,14 @@ kubedb dump [filename | S3 URI] [flags]
### Options inherited from parent commands

```
--context string Kubernetes context name
--dialect string Database dialect. One of (postgres|mariadb|mongodb) (default discovered)
--kubeconfig string Paths to the kubeconfig file (default "$HOME/.kube/config")
--log-format string Log formatter. One of (text|json) (default "text")
--log-level string Log level. One of (trace|debug|info|warning|error|fatal|panic) (default "info")
-n, --namespace string Kubernetes namespace
--pod string Perform detection from a pod instead of searching the namespace
--context string Kubernetes context name
--dialect string Database dialect. One of (postgres|mariadb|mongodb) (default discovered)
--healthchecks-ping-url string Notification handler URL
--kubeconfig string Paths to the kubeconfig file (default "$HOME/.kube/config")
--log-format string Log formatter. One of (text|json) (default "text")
--log-level string Log level. One of (trace|debug|info|warning|error|fatal|panic) (default "info")
-n, --namespace string Kubernetes namespace
--pod string Perform detection from a pod instead of searching the namespace
```

### SEE ALSO
Expand Down
15 changes: 8 additions & 7 deletions docs/kubedb_exec.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ kubedb exec [flags]
### Options inherited from parent commands

```
--context string Kubernetes context name
--dialect string Database dialect. One of (postgres|mariadb|mongodb) (default discovered)
--kubeconfig string Paths to the kubeconfig file (default "$HOME/.kube/config")
--log-format string Log formatter. One of (text|json) (default "text")
--log-level string Log level. One of (trace|debug|info|warning|error|fatal|panic) (default "info")
-n, --namespace string Kubernetes namespace
--pod string Perform detection from a pod instead of searching the namespace
--context string Kubernetes context name
--dialect string Database dialect. One of (postgres|mariadb|mongodb) (default discovered)
--healthchecks-ping-url string Notification handler URL
--kubeconfig string Paths to the kubeconfig file (default "$HOME/.kube/config")
--log-format string Log formatter. One of (text|json) (default "text")
--log-level string Log level. One of (trace|debug|info|warning|error|fatal|panic) (default "info")
-n, --namespace string Kubernetes namespace
--pod string Perform detection from a pod instead of searching the namespace
```

### SEE ALSO
Expand Down
15 changes: 8 additions & 7 deletions docs/kubedb_port-forward.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ kubedb port-forward [local_port] [flags]
### Options inherited from parent commands

```
--context string Kubernetes context name
--dialect string Database dialect. One of (postgres|mariadb|mongodb) (default discovered)
--kubeconfig string Paths to the kubeconfig file (default "$HOME/.kube/config")
--log-format string Log formatter. One of (text|json) (default "text")
--log-level string Log level. One of (trace|debug|info|warning|error|fatal|panic) (default "info")
-n, --namespace string Kubernetes namespace
--pod string Perform detection from a pod instead of searching the namespace
--context string Kubernetes context name
--dialect string Database dialect. One of (postgres|mariadb|mongodb) (default discovered)
--healthchecks-ping-url string Notification handler URL
--kubeconfig string Paths to the kubeconfig file (default "$HOME/.kube/config")
--log-format string Log formatter. One of (text|json) (default "text")
--log-level string Log level. One of (trace|debug|info|warning|error|fatal|panic) (default "info")
-n, --namespace string Kubernetes namespace
--pod string Perform detection from a pod instead of searching the namespace
```

### SEE ALSO
Expand Down
15 changes: 8 additions & 7 deletions docs/kubedb_restore.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,14 @@ kubedb restore filename [flags]
### Options inherited from parent commands

```
--context string Kubernetes context name
--dialect string Database dialect. One of (postgres|mariadb|mongodb) (default discovered)
--kubeconfig string Paths to the kubeconfig file (default "$HOME/.kube/config")
--log-format string Log formatter. One of (text|json) (default "text")
--log-level string Log level. One of (trace|debug|info|warning|error|fatal|panic) (default "info")
-n, --namespace string Kubernetes namespace
--pod string Perform detection from a pod instead of searching the namespace
--context string Kubernetes context name
--dialect string Database dialect. One of (postgres|mariadb|mongodb) (default discovered)
--healthchecks-ping-url string Notification handler URL
--kubeconfig string Paths to the kubeconfig file (default "$HOME/.kube/config")
--log-format string Log formatter. One of (text|json) (default "text")
--log-level string Log level. One of (trace|debug|info|warning|error|fatal|panic) (default "info")
-n, --namespace string Kubernetes namespace
--pod string Perform detection from a pod instead of searching the namespace
```

### SEE ALSO
Expand Down
15 changes: 8 additions & 7 deletions docs/kubedb_status.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ kubedb status [flags]
### Options inherited from parent commands

```
--context string Kubernetes context name
--dialect string Database dialect. One of (postgres|mariadb|mongodb) (default discovered)
--kubeconfig string Paths to the kubeconfig file (default "$HOME/.kube/config")
--log-format string Log formatter. One of (text|json) (default "text")
--log-level string Log level. One of (trace|debug|info|warning|error|fatal|panic) (default "info")
-n, --namespace string Kubernetes namespace
--pod string Perform detection from a pod instead of searching the namespace
--context string Kubernetes context name
--dialect string Database dialect. One of (postgres|mariadb|mongodb) (default discovered)
--healthchecks-ping-url string Notification handler URL
--kubeconfig string Paths to the kubeconfig file (default "$HOME/.kube/config")
--log-format string Log formatter. One of (text|json) (default "text")
--log-level string Log level. One of (trace|debug|info|warning|error|fatal|panic) (default "info")
-n, --namespace string Kubernetes namespace
--pod string Perform detection from a pod instead of searching the namespace
```

### SEE ALSO
Expand Down
10 changes: 10 additions & 0 deletions internal/config/flags/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,13 @@ func BindRedact(cmd *cobra.Command) {
panic(err)
}
}

func Healthchecks(cmd *cobra.Command) {
cmd.PersistentFlags().String(consts.HealthchecksPingUrlFlag, "", "Notification handler URL")
}

func BindHealthchecks(cmd *cobra.Command) {
if err := viper.BindPFlag(consts.HealthchecksPingUrlKey, cmd.Flags().Lookup(consts.HealthchecksPingUrlFlag)); err != nil {
panic(err)
}
}
9 changes: 5 additions & 4 deletions internal/consts/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ const (
JobPodLabelsFlag = "job-pod-labels"
NoJobFlag = "no-job"

QuietFlag = "quiet"
LogLevelFlag = "log-level"
LogFormatFlag = "log-format"
RedactFlag = "redact"
QuietFlag = "quiet"
LogLevelFlag = "log-level"
LogFormatFlag = "log-format"
RedactFlag = "redact"
HealthchecksPingUrlFlag = "healthchecks-ping-url"

RemoteGzipFlag = "remote-gzip"

Expand Down
23 changes: 12 additions & 11 deletions internal/consts/viper.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package consts

const (
AnalyzeKey = "restore.analyze"
HaltOnErrorKey = "restore.halt-on-error"
SpinnerKey = "spinner.name"
KubeconfigKey = "kubernetes.kubeconfig"
JobPodLabelsKey = "kubernetes.job-pod-labels"
NoJobKey = "kubernetes.no-job"
LogLevelKey = "log.level"
LogFormatKey = "log.format"
LogRedactKey = "log.redact"
RemoteGzipKey = "remote-gzip"
PortForwardAddrKey = "port-forward.address"
AnalyzeKey = "restore.analyze"
HaltOnErrorKey = "restore.halt-on-error"
SpinnerKey = "spinner.name"
KubeconfigKey = "kubernetes.kubeconfig"
JobPodLabelsKey = "kubernetes.job-pod-labels"
NoJobKey = "kubernetes.no-job"
LogLevelKey = "log.level"
LogFormatKey = "log.format"
LogRedactKey = "log.redact"
RemoteGzipKey = "remote-gzip"
PortForwardAddrKey = "port-forward.address"
HealthchecksPingUrlKey = "healthchecks.ping-url"
)
16 changes: 16 additions & 0 deletions internal/notifier/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package notifier

import "context"

type contextKey uint8

const notifierContextKey contextKey = iota

func NewContext(ctx context.Context, n Notifier) context.Context {
return context.WithValue(ctx, notifierContextKey, n)
}

func FromContext(ctx context.Context) (Notifier, bool) {
notifier, ok := ctx.Value(notifierContextKey).(Notifier)
return notifier, ok
}
73 changes: 73 additions & 0 deletions internal/notifier/healthchecks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package notifier

import (
"fmt"
"net/http"
"net/url"
"path"
"strings"

"github.com/clevyr/kubedb/internal/util"
)

func NewHealthchecks(url string) (Notifier, error) {
if url == "" {
return nil, fmt.Errorf("healthchecks %w", ErrEmptyUrl)
}

return &Healthchecks{
url: url,
}, nil
}

type Healthchecks struct {
url string
}

func (h Healthchecks) SendStatus(status Status, log string) error {
var statusStr string
switch status {
case StatusStart:
statusStr = "start"
case StatusFailure:
statusStr = "fail"
}

u, err := url.Parse(h.url)
if err != nil {
return err
}

u.Path = path.Join(u.Path, statusStr)

req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(log))
if err != nil {
return err
}

resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer util.ReadAndClose(resp.Body)

switch resp.StatusCode {
case http.StatusOK:
case http.StatusCreated:
default:
return fmt.Errorf("%w: %s", ErrInvalidResponse, resp.Status)
}
return nil
}

func (h Healthchecks) Started() error {
return h.SendStatus(StatusStart, "")
}

func (h Healthchecks) Finished(err error) error {
if err == nil {
return h.SendStatus(StatusSuccess, "")
} else {
return h.SendStatus(StatusFailure, "Error: "+err.Error())
}
}
35 changes: 35 additions & 0 deletions internal/notifier/notifier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package notifier

import (
"errors"
"fmt"
"strings"
)

type Status uint8

const (
StatusSuccess Status = iota
StatusFailure
StatusStart
)

var (
ErrInvalidResponse = errors.New("invalid http response")
ErrUnknownHandler = errors.New("unknown handler")
ErrEmptyUrl = errors.New("url must be set")
)

type Notifier interface {
Started() error
Finished(err error) error
}

func New(handler, url string) (Notifier, error) {
switch strings.ToLower(handler) {
case "healthchecks":
return NewHealthchecks(url)
default:
return nil, fmt.Errorf("%w: %s", ErrUnknownHandler, handler)
}
}
8 changes: 8 additions & 0 deletions internal/util/io.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package util

import "io"

func ReadAndClose(r io.ReadCloser) {
_, _ = io.Copy(io.Discard, r)
_ = r.Close()
}
Loading

0 comments on commit 9739d06

Please sign in to comment.