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

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))