2023-12-04 10:22:49 +00:00
|
|
|
package cmd
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log/slog"
|
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
|
2023-12-04 15:59:10 +00:00
|
|
|
"github.com/urfave/cli/v2"
|
|
|
|
|
2023-12-04 10:22:49 +00:00
|
|
|
"code.icb4dc0.de/prskr/nurse/check"
|
|
|
|
"code.icb4dc0.de/prskr/nurse/config"
|
|
|
|
"code.icb4dc0.de/prskr/nurse/protocols/http"
|
|
|
|
"code.icb4dc0.de/prskr/nurse/protocols/redis"
|
|
|
|
"code.icb4dc0.de/prskr/nurse/protocols/sql"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
defaultCheckTimeout = 500 * time.Millisecond
|
|
|
|
defaultAttemptCount = 20
|
|
|
|
)
|
|
|
|
|
2023-12-04 15:59:10 +00:00
|
|
|
const (
|
|
|
|
logLevelFlag = "log.level"
|
|
|
|
httpAddressFlag = "http.address"
|
|
|
|
httpReadHeaderTimeout = "http.read-header-timeout"
|
|
|
|
maxCheckAttemptsFlag = "check-attempts"
|
|
|
|
checkTimeoutFlag = "check-timeout"
|
|
|
|
serversFlag = "servers"
|
|
|
|
configFlag = "config"
|
|
|
|
)
|
|
|
|
|
2023-12-04 10:22:49 +00:00
|
|
|
func NewApp() (*cli.App, error) {
|
|
|
|
app := &app{
|
|
|
|
registry: check.NewRegistry(),
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := app.registry.Register(
|
|
|
|
redis.Module(),
|
|
|
|
http.Module(),
|
|
|
|
sql.Module(),
|
|
|
|
); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
srv := server{
|
|
|
|
app: app,
|
|
|
|
}
|
|
|
|
exec := executor{
|
|
|
|
app: app,
|
|
|
|
}
|
|
|
|
|
|
|
|
return &cli.App{
|
|
|
|
Name: "nurse",
|
|
|
|
DefaultCommand: "server",
|
|
|
|
EnableBashCompletion: true,
|
|
|
|
Before: app.init,
|
|
|
|
Flags: []cli.Flag{
|
|
|
|
&cli.StringFlag{
|
2023-12-04 15:59:10 +00:00
|
|
|
Name: configFlag,
|
2023-12-04 10:22:49 +00:00
|
|
|
Usage: "Config file to load, if not set `$HOME/.nurse.yaml`, `/etc/nurse/config.yaml` and `./nurse.yaml` are tried - optional",
|
|
|
|
Aliases: []string{"c"},
|
|
|
|
EnvVars: []string{"NURSE_CONFIG"},
|
|
|
|
},
|
|
|
|
&cli.DurationFlag{
|
2023-12-04 15:59:10 +00:00
|
|
|
Name: checkTimeoutFlag,
|
2023-12-04 10:22:49 +00:00
|
|
|
Usage: "Timeout when running checks",
|
|
|
|
Value: defaultCheckTimeout,
|
|
|
|
EnvVars: []string{"NURSE_CHECK_TIMEOUT"},
|
|
|
|
},
|
|
|
|
&cli.UintFlag{
|
2023-12-04 15:59:10 +00:00
|
|
|
Name: maxCheckAttemptsFlag,
|
2023-12-04 10:22:49 +00:00
|
|
|
Usage: "Number of attempts for a check",
|
|
|
|
Value: defaultAttemptCount,
|
|
|
|
EnvVars: []string{"NURSE_CHECK_ATTEMPTS"},
|
|
|
|
},
|
|
|
|
&cli.StringFlag{
|
2023-12-04 15:59:10 +00:00
|
|
|
Name: logLevelFlag,
|
2023-12-04 10:22:49 +00:00
|
|
|
Usage: "Log level to use",
|
|
|
|
Value: "info",
|
|
|
|
},
|
|
|
|
&cli.StringSliceFlag{
|
2023-12-04 15:59:10 +00:00
|
|
|
Name: serversFlag,
|
2023-12-04 10:22:49 +00:00
|
|
|
Usage: "",
|
|
|
|
Aliases: []string{"s"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Commands: []*cli.Command{
|
|
|
|
{
|
|
|
|
Name: "server",
|
|
|
|
Aliases: []string{"serve"},
|
|
|
|
Category: "daemon",
|
|
|
|
Action: srv.RunServer,
|
|
|
|
Flags: []cli.Flag{
|
|
|
|
&cli.StringSliceFlag{
|
|
|
|
Name: "endpoints",
|
2023-12-04 15:59:10 +00:00
|
|
|
Usage: "Endpoints to expose in the HTTP server",
|
2023-12-04 10:22:49 +00:00
|
|
|
Aliases: []string{"ep"},
|
|
|
|
},
|
2023-12-04 15:59:10 +00:00
|
|
|
&cli.StringFlag{
|
|
|
|
Name: httpAddressFlag,
|
|
|
|
Usage: "HTTP server address",
|
|
|
|
Value: ":8080",
|
|
|
|
EnvVars: []string{"NURSE_HTTP_ADDRESS"},
|
|
|
|
},
|
|
|
|
&cli.DurationFlag{
|
|
|
|
Name: httpReadHeaderTimeout,
|
|
|
|
Usage: "Timeout for reading headers in the HTTP server",
|
|
|
|
Value: 100 * time.Millisecond,
|
|
|
|
EnvVars: []string{"NURSE_HTTP_READ_HEADER_TIMEOUT"},
|
|
|
|
},
|
2023-12-04 10:22:49 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "exec-check",
|
|
|
|
Aliases: []string{"run-check"},
|
|
|
|
Category: "interactive",
|
|
|
|
Action: exec.ExecChecks,
|
|
|
|
ArgsUsage: "checks to execute, either in a single argument (within \"\") separated by a ';' or multiple arguments",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type app struct {
|
|
|
|
nurseInstance *config.Nurse
|
|
|
|
registry *check.Registry
|
|
|
|
lookup *config.ServerRegister
|
|
|
|
logging struct {
|
|
|
|
level slog.LevelVar
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *app) init(ctx *cli.Context) (err error) {
|
2023-12-04 15:59:10 +00:00
|
|
|
if err = a.configureLogging(ctx.String(logLevelFlag)); err != nil {
|
2023-12-04 10:22:49 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
a.nurseInstance, err = config.New(
|
2023-12-04 15:59:10 +00:00
|
|
|
config.WithCheckAttempts(ctx.Uint(maxCheckAttemptsFlag)),
|
|
|
|
config.WithCheckDuration(ctx.Duration(checkTimeoutFlag)),
|
|
|
|
config.WithConfigFile(ctx.String(configFlag)),
|
2023-12-04 10:22:49 +00:00
|
|
|
config.WithServersFromEnv(),
|
2023-12-04 15:59:10 +00:00
|
|
|
config.WithServersFromArgs(ctx.StringSlice(serversFlag)),
|
2023-12-04 10:22:49 +00:00
|
|
|
config.WithEndpointsFromEnv(),
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to load Nurse config: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if a.lookup, err = a.nurseInstance.ServerLookup(); err != nil {
|
|
|
|
return fmt.Errorf("failed to constructor server lookup: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a *app) configureLogging(level string) error {
|
|
|
|
if err := a.logging.level.UnmarshalText([]byte(level)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var addSource bool
|
|
|
|
if a.logging.level.Level() == slog.LevelDebug {
|
|
|
|
addSource = true
|
|
|
|
}
|
|
|
|
|
|
|
|
cfg := slog.HandlerOptions{
|
|
|
|
AddSource: addSource,
|
|
|
|
Level: a.logging.level.Level(),
|
|
|
|
}
|
|
|
|
|
|
|
|
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr, &cfg)))
|
|
|
|
return nil
|
|
|
|
}
|