refactor: make internals easier to test and add unit tests ()

This PR refactors most of the internals to make them easier to test (and also because the names didn't make sense). It adds unit tests for all internal components.

Reviewed-on: 
Co-authored-by: Emmanuel BENOÎT <tseeker@nocternity.net>
Co-committed-by: Emmanuel BENOÎT <tseeker@nocternity.net>
This commit is contained in:
Emmanuel BENOîT 2024-07-20 10:01:05 +02:00 committed by Emmanuel BENOîT
parent dcd732cc34
commit 78af496fe9
20 changed files with 939 additions and 369 deletions
cmd
sslcert
zoneserial

View file

@ -16,7 +16,7 @@ import (
"nocternity.net/gomonop/pkg/perfdata"
"nocternity.net/gomonop/pkg/plugin"
"nocternity.net/gomonop/pkg/program"
"nocternity.net/gomonop/pkg/results"
)
//--------------------------------------------------------------------------------------------------------
@ -167,7 +167,7 @@ type programFlags struct {
// Program data including configuration and runtime data.
type checkProgram struct {
programFlags // Flags from the command line
plugin *plugin.Plugin // Plugin output state
plugin *results.Results // Plugin output state
certificate *x509.Certificate // X.509 certificate from the server
}
@ -207,37 +207,37 @@ func (flags *programFlags) parseArguments() {
}
// Initialise the monitoring check program.
func NewProgram() program.Program {
func NewProgram() plugin.Plugin {
program := &checkProgram{
plugin: plugin.New("Certificate check"),
plugin: results.New("Certificate check"),
}
program.parseArguments()
return program
}
// Terminate the monitoring check program.
func (program *checkProgram) Done() {
program.plugin.Done()
// Return the program's output value.
func (program *checkProgram) Results() *results.Results {
return program.plugin
}
// Check the values that were specified from the command line. Returns true
// if the arguments made sense.
func (program *checkProgram) CheckArguments() bool {
if program.hostname == "" {
program.plugin.SetState(plugin.UNKNOWN, "no hostname specified")
program.plugin.SetState(results.StatusUnknown, "no hostname specified")
return false
}
if program.port < 1 || program.port > 65535 {
program.plugin.SetState(plugin.UNKNOWN, "invalid or missing port number")
program.plugin.SetState(results.StatusUnknown, "invalid or missing port number")
return false
}
if program.warn != -1 && program.crit != -1 && program.warn <= program.crit {
program.plugin.SetState(plugin.UNKNOWN, "nonsensical thresholds")
program.plugin.SetState(results.StatusUnknown, "nonsensical thresholds")
return false
}
if _, ok := certGetters[program.startTLS]; !ok {
errstr := "unsupported StartTLS protocol " + program.startTLS
program.plugin.SetState(plugin.UNKNOWN, errstr)
program.plugin.SetState(results.StatusUnknown, errstr)
return false
}
program.hostname = strings.ToLower(program.hostname)
@ -262,13 +262,13 @@ func (program *checkProgram) getCertificate() error {
// matches the requested host name.
func (program *checkProgram) checkSANlessCertificate() bool {
if !program.ignoreCnOnly || len(program.extraNames) != 0 {
program.plugin.SetState(plugin.WARNING,
program.plugin.SetState(results.StatusWarning,
"certificate doesn't have SAN domain names")
return false
}
dn := strings.ToLower(program.certificate.Subject.String())
if !strings.HasPrefix(dn, fmt.Sprintf("cn=%s,", program.hostname)) {
program.plugin.SetState(plugin.CRITICAL, "incorrect certificate CN")
program.plugin.SetState(results.StatusCritical, "incorrect certificate CN")
return false
}
return true
@ -298,7 +298,7 @@ func (program *checkProgram) checkNames() bool {
certificateIsOk = program.checkHostName(name) && certificateIsOk
}
if !certificateIsOk {
program.plugin.SetState(plugin.CRITICAL, "names missing from SAN domain names")
program.plugin.SetState(results.StatusCritical, "names missing from SAN domain names")
}
return certificateIsOk
}
@ -306,26 +306,26 @@ func (program *checkProgram) checkNames() bool {
// Check a certificate's time to expiry against the warning and critical
// thresholds, returning a status code and description based on these
// values.
func (program *checkProgram) checkCertificateExpiry(tlDays int) (plugin.Status, string) {
func (program *checkProgram) checkCertificateExpiry(tlDays int) (results.Status, string) {
if tlDays <= 0 {
return plugin.CRITICAL, "certificate expired"
return results.StatusCritical, "certificate expired"
}
var limitStr string
var state plugin.Status
var state results.Status
switch {
case program.crit > 0 && tlDays <= program.crit:
limitStr = fmt.Sprintf(" (<= %d)", program.crit)
state = plugin.CRITICAL
state = results.StatusCritical
case program.warn > 0 && tlDays <= program.warn:
limitStr = fmt.Sprintf(" (<= %d)", program.warn)
state = plugin.WARNING
state = results.StatusWarning
default:
limitStr = ""
state = plugin.OK
state = results.StatusOK
}
statusString := fmt.Sprintf("certificate will expire in %d days%s",
@ -338,10 +338,10 @@ func (program *checkProgram) checkCertificateExpiry(tlDays int) (plugin.Status,
func (program *checkProgram) setPerfData(tlDays int) {
pdat := perfdata.New("validity", perfdata.UomNone, strconv.Itoa(tlDays))
if program.crit > 0 {
pdat.SetCrit(perfdata.PDRMax(strconv.Itoa(program.crit)))
pdat.SetCrit(perfdata.RangeMax(strconv.Itoa(program.crit)))
}
if program.warn > 0 {
pdat.SetWarn(perfdata.PDRMax(strconv.Itoa(program.warn)))
pdat.SetWarn(perfdata.RangeMax(strconv.Itoa(program.warn)))
}
program.plugin.AddPerfData(pdat)
}
@ -351,7 +351,7 @@ func (program *checkProgram) setPerfData(tlDays int) {
func (program *checkProgram) RunCheck() {
err := program.getCertificate()
if err != nil {
program.plugin.SetState(plugin.UNKNOWN, err.Error())
program.plugin.SetState(results.StatusUnknown, err.Error())
} else if program.checkNames() {
timeLeft := time.Until(program.certificate.NotAfter)
tlDays := int((timeLeft + 86399*time.Second) / (24 * time.Hour))

View file

@ -14,7 +14,7 @@ import (
"nocternity.net/gomonop/pkg/perfdata"
"nocternity.net/gomonop/pkg/plugin"
"nocternity.net/gomonop/pkg/program"
"nocternity.net/gomonop/pkg/results"
)
//-------------------------------------------------------------------------------------------------------
@ -55,8 +55,8 @@ type programFlags struct {
// Program data including configuration and runtime data.
type checkProgram struct {
programFlags // Flags from the command line
plugin *plugin.Plugin // Plugin output state
programFlags // Flags from the command line
plugin *results.Results // Plugin output state
}
// Parse command line arguments and store their values. If the -h flag is present,
@ -77,43 +77,39 @@ func (flags *programFlags) parseArguments() {
}
// Initialise the monitoring check program.
func NewProgram() program.Program {
func NewProgram() plugin.Plugin {
program := &checkProgram{
plugin: plugin.New("DNS zone serial match check"),
plugin: results.New("DNS zone serial match check"),
}
program.parseArguments()
return program
}
// Terminate the monitoring check program.
func (program *checkProgram) Done() {
if r := recover(); r != nil {
program.plugin.SetState(plugin.UNKNOWN, "Internal error")
program.plugin.AddLinef("Error info: %v", r)
}
program.plugin.Done()
// Return the program's output value.
func (program *checkProgram) Results() *results.Results {
return program.plugin
}
// Check the values that were specified from the command line. Returns true if the arguments made sense.
func (program *checkProgram) CheckArguments() bool {
if program.hostname == "" {
program.plugin.SetState(plugin.UNKNOWN, "no DNS hostname specified")
program.plugin.SetState(results.StatusUnknown, "no DNS hostname specified")
return false
}
if program.port < 1 || program.port > 65535 {
program.plugin.SetState(plugin.UNKNOWN, "invalid DNS port number")
program.plugin.SetState(results.StatusUnknown, "invalid DNS port number")
return false
}
if program.zone == "" {
program.plugin.SetState(plugin.UNKNOWN, "no DNS zone specified")
program.plugin.SetState(results.StatusUnknown, "no DNS zone specified")
return false
}
if program.rsHostname == "" {
program.plugin.SetState(plugin.UNKNOWN, "no reference DNS hostname specified")
program.plugin.SetState(results.StatusUnknown, "no reference DNS hostname specified")
return false
}
if program.rsPort < 1 || program.rsPort > 65535 {
program.plugin.SetState(plugin.UNKNOWN, "invalid reference DNS port number")
program.plugin.SetState(results.StatusUnknown, "invalid reference DNS port number")
return false
}
program.hostname = strings.ToLower(program.hostname)
@ -180,12 +176,12 @@ func (program *checkProgram) RunCheck() {
cOk, cSerial := program.getSerial("checked", checkResponse)
rOk, rSerial := program.getSerial("reference", refResponse)
if !(cOk && rOk) {
program.plugin.SetState(plugin.UNKNOWN, "could not read serials")
program.plugin.SetState(results.StatusUnknown, "could not read serials")
return
}
if cSerial == rSerial {
program.plugin.SetState(plugin.OK, "serials match")
program.plugin.SetState(results.StatusOK, "serials match")
} else {
program.plugin.SetState(plugin.CRITICAL, "serials mismatch")
program.plugin.SetState(results.StatusCritical, "serials mismatch")
}
}