193 lines
4.4 KiB
Go
193 lines
4.4 KiB
Go
package poller
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
"golang.org/x/term"
|
|
)
|
|
|
|
// PrintRawMetrics prints raw json from the UniFi Controller. This is currently
|
|
// tied into the -j CLI arg, and is probably not very useful outside that context.
|
|
func (u *UnifiPoller) PrintRawMetrics() (err error) {
|
|
split := strings.SplitN(u.Flags.DumpJSON, " ", 2)
|
|
filter := &Filter{Kind: split[0]}
|
|
|
|
// Allows you to grab a controller other than 0 from config.
|
|
if split2 := strings.Split(filter.Kind, ":"); len(split2) > 1 {
|
|
filter.Kind = split2[0]
|
|
filter.Unit, _ = strconv.Atoi(split2[1])
|
|
}
|
|
|
|
// Used with "other"
|
|
if len(split) > 1 {
|
|
filter.Path = split[1]
|
|
}
|
|
|
|
// As of now we only have one input plugin, so target that [0].
|
|
m, err := inputs[0].RawMetrics(filter)
|
|
fmt.Println(string(m))
|
|
|
|
return err
|
|
}
|
|
|
|
// PrintPasswordHash prints a bcrypt'd password. Useful for the web server.
|
|
func (u *UnifiPoller) PrintPasswordHash() (err error) {
|
|
pwd := []byte(u.Flags.HashPW)
|
|
|
|
if u.Flags.HashPW == "-" {
|
|
fmt.Print("Enter Password: ")
|
|
|
|
pwd, err = term.ReadPassword(int(os.Stdin.Fd()))
|
|
if err != nil {
|
|
return fmt.Errorf("reading stdin: %w", err)
|
|
}
|
|
|
|
fmt.Println() // print a newline.
|
|
}
|
|
|
|
hash, err := bcrypt.GenerateFromPassword(pwd, bcrypt.MinCost)
|
|
fmt.Println(string(hash))
|
|
|
|
return err //nolint:wrapcheck
|
|
}
|
|
|
|
func (u *UnifiPoller) DebugIO() error {
|
|
inputSync.RLock()
|
|
defer inputSync.RUnlock()
|
|
|
|
outputSync.RLock()
|
|
defer outputSync.RUnlock()
|
|
|
|
allOK := true
|
|
|
|
var allErr error
|
|
|
|
u.Logf("Checking inputs...")
|
|
|
|
totalInputs := len(inputs)
|
|
|
|
for i, input := range inputs {
|
|
u.Logf("\t(%d/%d) Checking input %s...", i+1, totalInputs, input.Name)
|
|
|
|
ok, err := input.DebugInput()
|
|
if !ok {
|
|
u.LogErrorf("\t\t %s Failed: %v", input.Name, err)
|
|
|
|
allOK = false
|
|
} else {
|
|
u.Logf("\t\t %s is OK", input.Name)
|
|
}
|
|
|
|
if err != nil {
|
|
if allErr == nil {
|
|
allErr = err
|
|
} else {
|
|
allErr = fmt.Errorf("%v: %w", err, allErr)
|
|
}
|
|
}
|
|
}
|
|
|
|
u.Logf("Checking outputs...")
|
|
|
|
totalOutputs := len(outputs)
|
|
|
|
for i, output := range outputs {
|
|
u.Logf("\t(%d/%d) Checking output %s...", i+1, totalOutputs, output.Name)
|
|
|
|
ok, err := output.DebugOutput()
|
|
if !ok {
|
|
u.LogErrorf("\t\t %s Failed: %v", output.Name, err)
|
|
|
|
allOK = false
|
|
} else {
|
|
u.Logf("\t\t %s is OK", output.Name)
|
|
}
|
|
|
|
if err != nil {
|
|
if allErr == nil {
|
|
allErr = err
|
|
} else {
|
|
allErr = fmt.Errorf("%v: %w", err, allErr)
|
|
}
|
|
}
|
|
}
|
|
|
|
if !allOK {
|
|
u.LogErrorf("No all checks passed, please fix the logged issues.")
|
|
}
|
|
|
|
return allErr
|
|
}
|
|
|
|
// HealthCheck performs a basic health check suitable for Docker HEALTHCHECK.
|
|
// It validates configuration and checks if inputs/outputs are properly configured.
|
|
// Returns nil (exit 0) if healthy, error (exit 1) if unhealthy.
|
|
func (u *UnifiPoller) HealthCheck() error {
|
|
// Enable health check mode to skip network binding in output validation.
|
|
SetHealthCheckMode(true)
|
|
defer SetHealthCheckMode(false)
|
|
|
|
// Silence output for health checks (Docker doesn't need verbose logs).
|
|
u.Quiet = true
|
|
|
|
// Load configuration.
|
|
cfile, err := getFirstFile(strings.Split(u.Flags.ConfigFile, ","))
|
|
if err != nil {
|
|
return fmt.Errorf("health check failed: config file not found: %w", err)
|
|
}
|
|
|
|
u.Flags.ConfigFile = cfile
|
|
|
|
if err := u.ParseConfigs(); err != nil {
|
|
return fmt.Errorf("health check failed: config parse error: %w", err)
|
|
}
|
|
|
|
inputSync.RLock()
|
|
defer inputSync.RUnlock()
|
|
|
|
outputSync.RLock()
|
|
defer outputSync.RUnlock()
|
|
|
|
// Check that we have at least one input and one output configured.
|
|
if len(inputs) == 0 {
|
|
return fmt.Errorf("health check failed: no input plugins configured")
|
|
}
|
|
|
|
if len(outputs) == 0 {
|
|
return fmt.Errorf("health check failed: no output plugins configured")
|
|
}
|
|
|
|
// Check if at least one output is enabled.
|
|
hasEnabledOutput := false
|
|
for _, output := range outputs {
|
|
if output.Enabled() {
|
|
hasEnabledOutput = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !hasEnabledOutput {
|
|
return fmt.Errorf("health check failed: no enabled output plugins")
|
|
}
|
|
|
|
// Perform configuration validation on enabled outputs.
|
|
// Network binding checks will be skipped automatically due to health check mode.
|
|
for _, output := range outputs {
|
|
if !output.Enabled() {
|
|
continue
|
|
}
|
|
|
|
ok, err := output.DebugOutput()
|
|
if !ok || err != nil {
|
|
return fmt.Errorf("health check failed: output %s validation failed: %w", output.Name, err)
|
|
}
|
|
}
|
|
|
|
// All checks passed, application is healthy.
|
|
return nil
|
|
}
|