refactor: reorganize project in order to include automation #1

Merged
Emmanuel BENOîT merged 19 commits from :master into master 2024-07-19 22:01:35 +02:00
7 changed files with 191 additions and 163 deletions
Showing only changes of commit 68b88bc766 - Show all commits

View file

@ -4,19 +4,19 @@ import (
"bufio" "bufio"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"errors"
"fmt" "fmt"
"net" "net"
"net/textproto" "net/textproto"
"os" "os"
"strconv"
"strings" "strings"
"time" "time"
"github.com/karrick/golf"
"nocternity.net/gomonop/pkg/perfdata" "nocternity.net/gomonop/pkg/perfdata"
"nocternity.net/gomonop/pkg/plugin" "nocternity.net/gomonop/pkg/plugin"
"nocternity.net/gomonop/pkg/program" "nocternity.net/gomonop/pkg/program"
"github.com/karrick/golf"
) )
//-------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------
@ -26,7 +26,7 @@ type certGetter interface {
getCertificate(tlsConfig *tls.Config, address string) (*x509.Certificate, error) getCertificate(tlsConfig *tls.Config, address string) (*x509.Certificate, error)
} }
// Full TLS certificate fetcher // Full TLS certificate fetcher.
type fullTLSGetter struct{} type fullTLSGetter struct{}
func (f fullTLSGetter) getCertificate(tlsConfig *tls.Config, address string) (*x509.Certificate, error) { func (f fullTLSGetter) getCertificate(tlsConfig *tls.Config, address string) (*x509.Certificate, error) {
@ -41,17 +41,18 @@ func (f fullTLSGetter) getCertificate(tlsConfig *tls.Config, address string) (*x
return conn.ConnectionState().PeerCertificates[0], nil return conn.ConnectionState().PeerCertificates[0], nil
} }
// SMTP+STARTTLS certificate getter // SMTP+STARTTLS certificate getter.
type smtpGetter struct{} type smtpGetter struct{}
func (f smtpGetter) cmd(tcon *textproto.Conn, expectCode int, text string) (int, string, error) { func (f smtpGetter) cmd(tcon *textproto.Conn, expectCode int, text string) error {
id, err := tcon.Cmd("%s", text) id, err := tcon.Cmd("%s", text)
if err != nil { if err != nil {
return 0, "", err return err
} }
tcon.StartResponse(id) tcon.StartResponse(id)
defer tcon.EndResponse(id) defer tcon.EndResponse(id)
return tcon.ReadResponse(expectCode) _, _, err = tcon.ReadResponse(expectCode)
return err
} }
func (f smtpGetter) getCertificate(tlsConfig *tls.Config, address string) (*x509.Certificate, error) { func (f smtpGetter) getCertificate(tlsConfig *tls.Config, address string) (*x509.Certificate, error) {
@ -64,10 +65,10 @@ func (f smtpGetter) getCertificate(tlsConfig *tls.Config, address string) (*x509
if _, _, err := text.ReadResponse(220); err != nil { if _, _, err := text.ReadResponse(220); err != nil {
return nil, err return nil, err
} }
if _, _, err := f.cmd(text, 250, "HELO localhost"); err != nil { if err := f.cmd(text, 250, "HELO localhost"); err != nil {
return nil, err return nil, err
} }
if _, _, err := f.cmd(text, 220, "STARTTLS"); err != nil { if err := f.cmd(text, 220, "STARTTLS"); err != nil {
return nil, err return nil, err
} }
t := tls.Client(conn, tlsConfig) t := tls.Client(conn, tlsConfig)
@ -77,9 +78,17 @@ func (f smtpGetter) getCertificate(tlsConfig *tls.Config, address string) (*x509
return t.ConnectionState().PeerCertificates[0], nil return t.ConnectionState().PeerCertificates[0], nil
} }
// ManageSieve STARTTLS certificate getter // ManageSieve STARTTLS certificate getter.
type sieveGetter struct{} type sieveGetter struct{}
type sieveError struct {
msg string
}
func (e sieveError) Error() string {
return "Sieve error: " + e.msg
}
func (f sieveGetter) waitOK(conn net.Conn) error { func (f sieveGetter) waitOK(conn net.Conn) error {
scanner := bufio.NewScanner(conn) scanner := bufio.NewScanner(conn)
for scanner.Scan() { for scanner.Scan() {
@ -88,10 +97,10 @@ func (f sieveGetter) waitOK(conn net.Conn) error {
return nil return nil
} }
if strings.HasPrefix(line, "NO ") { if strings.HasPrefix(line, "NO ") {
return errors.New(line[3:]) return sieveError{msg: line[3:]}
} }
if strings.HasPrefix(line, "BYE ") { if strings.HasPrefix(line, "BYE ") {
return errors.New(line[4:]) return sieveError{msg: line[4:]}
} }
} }
return scanner.Err() return scanner.Err()
@ -123,23 +132,23 @@ func (f sieveGetter) getCertificate(tlsConfig *tls.Config, address string) (*x50
return t.ConnectionState().PeerCertificates[0], nil return t.ConnectionState().PeerCertificates[0], nil
} }
// Supported StartTLS protocols // Supported StartTLS protocols.
var certGetters map[string]certGetter = map[string]certGetter{ var certGetters = map[string]certGetter{
"": fullTLSGetter{}, "": fullTLSGetter{},
"smtp": &smtpGetter{}, "smtp": &smtpGetter{},
"sieve": &sieveGetter{}, "sieve": &sieveGetter{},
} }
// Get a string that represents supported StartTLS protocols // Get a string that represents supported StartTLS protocols.
func listSupportedGetters() string { func listSupportedGetters() string {
sb := strings.Builder{} strBuilder := strings.Builder{}
for key := range certGetters { for key := range certGetters {
if sb.Len() != 0 { if strBuilder.Len() != 0 {
sb.WriteString(", ") strBuilder.WriteString(", ")
} }
sb.WriteString(key) strBuilder.WriteString(key)
} }
return sb.String() return strBuilder.String()
} }
//-------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------
@ -227,7 +236,7 @@ func (program *checkProgram) CheckArguments() bool {
return false return false
} }
if _, ok := certGetters[program.startTLS]; !ok { if _, ok := certGetters[program.startTLS]; !ok {
errstr := fmt.Sprintf("unsupported StartTLS protocol %s", program.startTLS) errstr := "unsupported StartTLS protocol " + program.startTLS
program.plugin.SetState(plugin.UNKNOWN, errstr) program.plugin.SetState(plugin.UNKNOWN, errstr)
return false return false
} }
@ -239,6 +248,7 @@ func (program *checkProgram) CheckArguments() bool {
// if connecting or performing the TLS handshake fail. // if connecting or performing the TLS handshake fail.
func (program *checkProgram) getCertificate() error { func (program *checkProgram) getCertificate() error {
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
//nolint:gosec // The whole point is to read the certificate.
InsecureSkipVerify: true, InsecureSkipVerify: true,
MinVersion: tls.VersionTLS10, MinVersion: tls.VersionTLS10,
} }
@ -273,7 +283,7 @@ func (program *checkProgram) checkHostName(name string) bool {
return true return true
} }
} }
program.plugin.AddLine(fmt.Sprintf("missing DNS name %s in certificate", name)) program.plugin.AddLine("missing DNS name " + name + " in certificate")
return false return false
} }
@ -283,35 +293,41 @@ func (program *checkProgram) checkNames() bool {
if len(program.certificate.DNSNames) == 0 { if len(program.certificate.DNSNames) == 0 {
return program.checkSANlessCertificate() return program.checkSANlessCertificate()
} }
ok := program.checkHostName(program.hostname) certificateIsOk := program.checkHostName(program.hostname)
for _, name := range program.extraNames { for _, name := range program.extraNames {
ok = program.checkHostName(name) && ok certificateIsOk = program.checkHostName(name) && certificateIsOk
} }
if !ok { if !certificateIsOk {
program.plugin.SetState(plugin.CRITICAL, "names missing from SAN domain names") program.plugin.SetState(plugin.CRITICAL, "names missing from SAN domain names")
} }
return ok return certificateIsOk
} }
// Check a certificate's time to expiry agains the warning and critical // Check a certificate's time to expiry against the warning and critical
// thresholds, returning a status code and description based on these // thresholds, returning a status code and description based on these
// values. // values.
func (program *checkProgram) checkCertificateExpiry(tlDays int) (plugin.Status, string) { func (program *checkProgram) checkCertificateExpiry(tlDays int) (plugin.Status, string) {
if tlDays <= 0 { if tlDays <= 0 {
return plugin.CRITICAL, "certificate expired" return plugin.CRITICAL, "certificate expired"
} }
var limitStr string var limitStr string
var state plugin.Status var state plugin.Status
if program.crit > 0 && tlDays <= program.crit {
switch {
case program.crit > 0 && tlDays <= program.crit:
limitStr = fmt.Sprintf(" (<= %d)", program.crit) limitStr = fmt.Sprintf(" (<= %d)", program.crit)
state = plugin.CRITICAL state = plugin.CRITICAL
} else if program.warn > 0 && tlDays <= program.warn {
case program.warn > 0 && tlDays <= program.warn:
limitStr = fmt.Sprintf(" (<= %d)", program.warn) limitStr = fmt.Sprintf(" (<= %d)", program.warn)
state = plugin.WARNING state = plugin.WARNING
} else {
default:
limitStr = "" limitStr = ""
state = plugin.OK state = plugin.OK
} }
statusString := fmt.Sprintf("certificate will expire in %d days%s", statusString := fmt.Sprintf("certificate will expire in %d days%s",
tlDays, limitStr) tlDays, limitStr)
return state, statusString return state, statusString
@ -320,12 +336,12 @@ func (program *checkProgram) checkCertificateExpiry(tlDays int) (plugin.Status,
// Set the plugin's performance data based on the time left before the // Set the plugin's performance data based on the time left before the
// certificate expires and the thresholds. // certificate expires and the thresholds.
func (program *checkProgram) setPerfData(tlDays int) { func (program *checkProgram) setPerfData(tlDays int) {
pdat := perfdata.New("validity", perfdata.UOM_NONE, fmt.Sprintf("%d", tlDays)) pdat := perfdata.New("validity", perfdata.UomNone, strconv.Itoa(tlDays))
if program.crit > 0 { if program.crit > 0 {
pdat.SetCrit(perfdata.PDRMax(fmt.Sprint(program.crit))) pdat.SetCrit(perfdata.PDRMax(strconv.Itoa(program.crit)))
} }
if program.warn > 0 { if program.warn > 0 {
pdat.SetWarn(perfdata.PDRMax(fmt.Sprint(program.warn))) pdat.SetWarn(perfdata.PDRMax(strconv.Itoa(program.warn)))
} }
program.plugin.AddPerfData(pdat) program.plugin.AddPerfData(pdat)
} }

View file

@ -5,15 +5,16 @@ import (
"net" "net"
"os" "os"
"reflect" "reflect"
"strconv"
"strings" "strings"
"time" "time"
"github.com/karrick/golf"
"github.com/miekg/dns"
"nocternity.net/gomonop/pkg/perfdata" "nocternity.net/gomonop/pkg/perfdata"
"nocternity.net/gomonop/pkg/plugin" "nocternity.net/gomonop/pkg/plugin"
"nocternity.net/gomonop/pkg/program" "nocternity.net/gomonop/pkg/program"
"github.com/karrick/golf"
"github.com/miekg/dns"
) )
//------------------------------------------------------------------------------------------------------- //-------------------------------------------------------------------------------------------------------
@ -33,7 +34,7 @@ type (
// Query a zone's SOA record through a given DNS and return the response using the channel. // Query a zone's SOA record through a given DNS and return the response using the channel.
func queryZoneSOA(dnsq *dns.Msg, hostname string, port int, output responseChannel) { func queryZoneSOA(dnsq *dns.Msg, hostname string, port int, output responseChannel) {
dnsc := new(dns.Client) dnsc := new(dns.Client)
in, rtt, err := dnsc.Exchange(dnsq, net.JoinHostPort(hostname, fmt.Sprintf("%d", port))) in, rtt, err := dnsc.Exchange(dnsq, net.JoinHostPort(hostname, strconv.Itoa(port)))
output <- queryResponse{ output <- queryResponse{
data: in, data: in,
rtt: rtt, rtt: rtt,
@ -88,7 +89,7 @@ func NewProgram() program.Program {
func (program *checkProgram) Done() { func (program *checkProgram) Done() {
if r := recover(); r != nil { if r := recover(); r != nil {
program.plugin.SetState(plugin.UNKNOWN, "Internal error") program.plugin.SetState(plugin.UNKNOWN, "Internal error")
program.plugin.AddLine("Error info: %v", r) program.plugin.AddLinef("Error info: %v", r)
} }
program.plugin.Done() program.plugin.Done()
} }
@ -131,7 +132,7 @@ func (program *checkProgram) queryServers() (queryResponse, queryResponse) {
go queryZoneSOA(dnsq, program.hostname, program.port, checkOut) go queryZoneSOA(dnsq, program.hostname, program.port, checkOut)
go queryZoneSOA(dnsq, program.rsHostname, program.rsPort, refOut) go queryZoneSOA(dnsq, program.rsHostname, program.rsPort, refOut)
var checkResponse, refResponse queryResponse var checkResponse, refResponse queryResponse
for i := 0; i < 2; i++ { for range 2 {
select { select {
case m := <-checkOut: case m := <-checkOut:
checkResponse = m checkResponse = m
@ -145,7 +146,7 @@ func (program *checkProgram) queryServers() (queryResponse, queryResponse) {
// Add a server's RTT to the performance data. // Add a server's RTT to the performance data.
func (program *checkProgram) addRttPerf(name string, value time.Duration) { func (program *checkProgram) addRttPerf(name string, value time.Duration) {
s := fmt.Sprintf("%f", value.Seconds()) s := fmt.Sprintf("%f", value.Seconds())
pd := perfdata.New(name, perfdata.UOM_SECONDS, s) pd := perfdata.New(name, perfdata.UomSeconds, s)
program.plugin.AddPerfData(pd) program.plugin.AddPerfData(pd)
} }
@ -155,20 +156,20 @@ func (program *checkProgram) addRttPerf(name string, value time.Duration) {
// successful. // successful.
func (program *checkProgram) getSerial(server string, response queryResponse) (ok bool, serial uint32) { func (program *checkProgram) getSerial(server string, response queryResponse) (ok bool, serial uint32) {
if response.err != nil { if response.err != nil {
program.plugin.AddLine("%s server error : %s", server, response.err) program.plugin.AddLinef("%s server error : %s", server, response.err)
return false, 0 return false, 0
} }
program.addRttPerf(fmt.Sprintf("%s_rtt", server), response.rtt) program.addRttPerf(server+"_rtt", response.rtt)
if len(response.data.Answer) != 1 { if len(response.data.Answer) != 1 {
program.plugin.AddLine("%s server did not return exactly one record", server) program.plugin.AddLine(server + " server did not return exactly one record")
return false, 0 return false, 0
} }
if soa, ok := response.data.Answer[0].(*dns.SOA); ok { if soa, ok := response.data.Answer[0].(*dns.SOA); ok {
program.plugin.AddLine("serial on %s server: %d", server, soa.Serial) program.plugin.AddLinef("serial on %s server: %d", server, soa.Serial)
return true, soa.Serial return true, soa.Serial
} }
t := reflect.TypeOf(response.data.Answer[0]) t := reflect.TypeOf(response.data.Answer[0])
program.plugin.AddLine("%s server did not return SOA record; record type: %v", server, t) program.plugin.AddLinef("%s server did not return SOA record; record type: %v", server, t)
return false, 0 return false, 0
} }

53
main.go
View file

@ -12,46 +12,49 @@ import (
) )
var ( var (
programs map[string]program.ProgramBuilder = map[string]program.ProgramBuilder{ programs map[string]program.Builder = map[string]program.Builder{
"check_ssl_certificate": sslcert.NewProgram, "check_ssl_certificate": sslcert.NewProgram,
"check_zone_serial": zoneserial.NewProgram, "check_zone_serial": zoneserial.NewProgram,
} }
) )
func exitError(msg string, args ...interface{}) { func getProgram() program.Program {
fmt.Fprintf(os.Stderr, msg+"\n", args...)
os.Exit(1)
}
func main() {
ownName := filepath.Base(os.Args[0]) ownName := filepath.Base(os.Args[0])
var program program.Program
if builder, ok := programs[ownName]; ok { if builder, ok := programs[ownName]; ok {
program = builder() return builder()
} else if len(os.Args) < 2 { }
exitError(
""+ if len(os.Args) < 2 {
"Syntax: %s <program> [arguments]\n"+ fmt.Printf("Syntax: %s <program> [arguments]\n", ownName)
" %s --programs|-p\n"+ fmt.Printf(" %s --programs|-p\n", ownName)
" %s --version|-v", fmt.Printf(" %s --version|-v", ownName)
os.Args[0], os.Args[0], os.Args[0]) }
} else if os.Args[1] == "--programs" || os.Args[1] == "-p" {
switch os.Args[1] {
case "--programs", "-p":
for name := range programs { for name := range programs {
fmt.Println(name) fmt.Println(name)
} }
os.Exit(0) os.Exit(0)
} else if os.Args[1] == "--version" || os.Args[1] == "-v" {
fmt.Printf("gomonop %s\n", version.Version())
os.Exit(0)
} else if builder, ok := programs[os.Args[1]]; ok { case "--version", "-v":
os.Args = os.Args[1:] fmt.Printf("%s %s\n", ownName, version.Version())
program = builder() os.Exit(0)
} else {
exitError("Unknown program: %s", os.Args[1])
} }
if builder, ok := programs[os.Args[1]]; ok {
os.Args = os.Args[1:]
return builder()
}
fmt.Printf("Unknown program: %s\n", os.Args[1])
os.Exit(1)
return nil
}
func main() {
program := getProgram()
defer program.Done() defer program.Done()
if program.CheckArguments() { if program.CheckArguments() {
program.RunCheck() program.RunCheck()

View file

@ -12,15 +12,15 @@ import (
type UnitOfMeasurement int type UnitOfMeasurement int
const ( const (
UOM_NONE UnitOfMeasurement = iota UomNone UnitOfMeasurement = iota
UOM_SECONDS UomSeconds
UOM_PERCENT UomPercent
UOM_BYTES UomBytes
UOM_KILOBYTES UomKilobytes
UOM_MEGABYTES UomMegabytes
UOM_GIGABYTES UomGigabytes
UOM_TERABYTES UomTerabytes
UOM_COUNTER UomCounter
) )
func (u UnitOfMeasurement) String() string { func (u UnitOfMeasurement) String() string {
@ -31,24 +31,24 @@ func (u UnitOfMeasurement) String() string {
type perfDataBits int type perfDataBits int
const ( const (
PDAT_WARN perfDataBits = 1 << iota PDatWarn perfDataBits = 1 << iota
PDAT_CRIT PDatCrit
PDAT_MIN PDatMin
PDAT_MAX PDatMax
) )
// Regexps used to check values and ranges in performance data records. // Regexps used to check values and ranges in performance data records.
var ( var (
// Common value check regexp // Common value check regexp.
vcRegexp = `^-?(0(\.\d*)?|[1-9]\d*(\.\d*)?|\.\d+)$` vcRegexp = `^-?(0(\.\d*)?|[1-9]\d*(\.\d*)?|\.\d+)$`
// Compiled value check regexp // Compiled value check regexp.
valueCheck = regexp.MustCompile(vcRegexp) valueCheck = regexp.MustCompile(vcRegexp)
// Compiled range min value check // Compiled range min value check.
rangeMinCheck = regexp.MustCompile(vcRegexp + `|^~$`) rangeMinCheck = regexp.MustCompile(vcRegexp + `|^~$`)
) )
// Performance data range // Performance data range.
type PerfDataRange struct { type PDRange struct {
start string start string
end string end string
inside bool inside bool
@ -56,54 +56,56 @@ type PerfDataRange struct {
// Creates a performance data range from -inf to 0 and from the specified // Creates a performance data range from -inf to 0 and from the specified
// value to +inf. // value to +inf.
func PDRMax(max string) *PerfDataRange { func PDRMax(max string) *PDRange {
if !valueCheck.MatchString(max) { if !valueCheck.MatchString(max) {
panic("invalid performance data range maximum value") panic("invalid performance data range maximum value")
} }
r := &PerfDataRange{} pdRange := &PDRange{}
r.start = "0" pdRange.start = "0"
r.end = max pdRange.end = max
return r return pdRange
} }
// Creates a performance data range from -inf to the specified minimal value // Creates a performance data range from -inf to the specified minimal value
// and from the specified maximal value to +inf. // and from the specified maximal value to +inf.
func PDRMinMax(min, max string) *PerfDataRange { func PDRMinMax(min, max string) *PDRange {
if !valueCheck.MatchString(max) { if !valueCheck.MatchString(max) {
panic("invalid performance data range maximum value") panic("invalid performance data range maximum value")
} }
if !rangeMinCheck.MatchString(min) { if !rangeMinCheck.MatchString(min) {
panic("invalid performance data range minimum value") panic("invalid performance data range minimum value")
} }
r := &PerfDataRange{} pdRange := &PDRange{}
r.start = min pdRange.start = min
r.end = max pdRange.end = max
return r return pdRange
} }
// Inverts the range. // Inverts the range.
func (r *PerfDataRange) Inside() *PerfDataRange { func (r *PDRange) Inside() *PDRange {
r.inside = true r.inside = true
return r return r
} }
// Generates the range's string representation so it can be sent to the // Generates the range's string representation so it can be sent to the
// monitoring system. // monitoring system.
func (r *PerfDataRange) String() string { func (r *PDRange) String() string {
var start, inside string var start, inside string
if r.start == "" {
switch r.start {
case "":
start = "~" start = "~"
} else if r.start == "0" { case "0":
start = "" start = ""
} else { default:
start = r.start start = r.start
} }
if r.inside { if r.inside {
inside = "@" inside = "@"
} else {
inside = ""
} }
return fmt.Sprintf("%s%s:%s", inside, start, r.end)
return inside + start + ":" + r.end
} }
// Performance data, including a label, units, a value, warning/critical // Performance data, including a label, units, a value, warning/critical
@ -113,7 +115,7 @@ type PerfData struct {
units UnitOfMeasurement units UnitOfMeasurement
bits perfDataBits bits perfDataBits
value string value string
warn, crit PerfDataRange warn, crit PDRange
min, max string min, max string
} }
@ -122,36 +124,36 @@ func New(label string, units UnitOfMeasurement, value string) *PerfData {
if value != "" && !valueCheck.MatchString(value) { if value != "" && !valueCheck.MatchString(value) {
panic("invalid value") panic("invalid value")
} }
r := &PerfData{} pdRange := &PerfData{}
r.Label = label pdRange.Label = label
r.units = units pdRange.units = units
if value == "" { if value == "" {
r.value = "U" pdRange.value = "U"
} else { } else {
r.value = value pdRange.value = value
} }
return r return pdRange
} }
// Set the warning range for the performance data record. // Set the warning range for the performance data record.
func (d *PerfData) SetWarn(r *PerfDataRange) { func (d *PerfData) SetWarn(r *PDRange) {
d.warn = *r d.warn = *r
d.bits = d.bits | PDAT_WARN d.bits |= PDatWarn
} }
// Set the critical range for the performance data record. // Set the critical range for the performance data record.
func (d *PerfData) SetCrit(r *PerfDataRange) { func (d *PerfData) SetCrit(r *PDRange) {
d.crit = *r d.crit = *r
d.bits = d.bits | PDAT_CRIT d.bits |= PDatCrit
} }
// Set the performance data's minimal value // Set the performance data's minimal value.
func (d *PerfData) SetMin(min string) { func (d *PerfData) SetMin(min string) {
if !valueCheck.MatchString(min) { if !valueCheck.MatchString(min) {
panic("invalid value") panic("invalid value")
} }
d.min = min d.min = min
d.bits = d.bits | PDAT_MIN d.bits |= PDatMin
} }
// Set the performance data's maximal value. // Set the performance data's maximal value.
@ -160,38 +162,38 @@ func (d *PerfData) SetMax(max string) {
panic("invalid value") panic("invalid value")
} }
d.max = max d.max = max
d.bits = d.bits | PDAT_MAX d.bits |= PDatMax
} }
// Converts performance data to a string which may be read by the monitoring // Converts performance data to a string which may be read by the monitoring
// system. // system.
func (d *PerfData) String() string { func (d *PerfData) String() string {
var sb strings.Builder var strBuilder strings.Builder
needsQuotes := strings.ContainsAny(d.Label, " '=\"") needsQuotes := strings.ContainsAny(d.Label, " '=\"")
if needsQuotes { if needsQuotes {
sb.WriteString("'") strBuilder.WriteString("'")
} }
sb.WriteString(strings.ReplaceAll(d.Label, "'", "''")) strBuilder.WriteString(strings.ReplaceAll(d.Label, "'", "''"))
if needsQuotes { if needsQuotes {
sb.WriteString("'") strBuilder.WriteString("'")
} }
sb.WriteString("=") strBuilder.WriteString("=")
sb.WriteString(fmt.Sprintf("%s%s;", d.value, d.units.String())) strBuilder.WriteString(fmt.Sprintf("%s%s;", d.value, d.units.String()))
if d.bits&PDAT_WARN != 0 { if d.bits&PDatWarn != 0 {
sb.WriteString(d.warn.String()) strBuilder.WriteString(d.warn.String())
} }
sb.WriteString(";") strBuilder.WriteString(";")
if d.bits&PDAT_CRIT != 0 { if d.bits&PDatCrit != 0 {
sb.WriteString(d.crit.String()) strBuilder.WriteString(d.crit.String())
} }
sb.WriteString(";") strBuilder.WriteString(";")
if d.bits&PDAT_MIN != 0 { if d.bits&PDatMin != 0 {
sb.WriteString(d.min) strBuilder.WriteString(d.min)
} }
sb.WriteString(";") strBuilder.WriteString(";")
if d.bits&PDAT_MAX != 0 { if d.bits&PDatMax != 0 {
sb.WriteString(d.max) strBuilder.WriteString(d.max)
} }
return sb.String() return strBuilder.String()
} }

View file

@ -78,7 +78,7 @@ func TestPdrMinMax(t *testing.T) {
} }
func TestPdrInside(t *testing.T) { func TestPdrInside(t *testing.T) {
pdr := &PerfDataRange{} pdr := &PDRange{}
pdr = pdr.Inside() pdr = pdr.Inside()
assert(t, pdr.inside, "Inside flag should be set") assert(t, pdr.inside, "Inside flag should be set")
pdr = pdr.Inside() pdr = pdr.Inside()
@ -87,14 +87,14 @@ func TestPdrInside(t *testing.T) {
func TestPdrString(t *testing.T) { func TestPdrString(t *testing.T) {
type Test struct { type Test struct {
pdr PerfDataRange pdr PDRange
out string out string
} }
tests := []Test{ tests := []Test{
{pdr: PerfDataRange{start: "Y", end: "X"}, out: "Y:X"}, {pdr: PDRange{start: "Y", end: "X"}, out: "Y:X"},
{pdr: PerfDataRange{end: "X"}, out: "~:X"}, {pdr: PDRange{end: "X"}, out: "~:X"},
{pdr: PerfDataRange{start: "0", end: "X"}, out: ":X"}, {pdr: PDRange{start: "0", end: "X"}, out: ":X"},
{pdr: PerfDataRange{inside: true, start: "Y", end: "X"}, out: "@Y:X"}, {pdr: PDRange{inside: true, start: "Y", end: "X"}, out: "@Y:X"},
} }
for _, test := range tests { for _, test := range tests {

View file

@ -58,17 +58,22 @@ func (p *Plugin) SetState(status Status, message string) {
} }
// AddLine adds the specified string to the extra output text buffer. // AddLine adds the specified string to the extra output text buffer.
func (p *Plugin) AddLine(format string, data ...interface{}) { func (p *Plugin) AddLine(line string) {
if p.extraText == nil { if p.extraText == nil {
p.extraText = list.New() p.extraText = list.New()
} }
p.extraText.PushBack(fmt.Sprintf(format, data...)) p.extraText.PushBack(line)
}
// AddLinef formats the input and adds it to the text buffer.
func (p *Plugin) AddLinef(format string, data ...interface{}) {
p.AddLine(fmt.Sprintf(format, data...))
} }
// AddLines add the specified `lines` to the output text. // AddLines add the specified `lines` to the output text.
func (p *Plugin) AddLines(lines []string) { func (p *Plugin) AddLines(lines []string) {
for _, line := range lines { for _, line := range lines {
p.AddLine("%s", line) p.AddLine(line)
} }
} }
@ -78,7 +83,7 @@ func (p *Plugin) AddLines(lines []string) {
func (p *Plugin) AddPerfData(pd *perfdata.PerfData) { func (p *Plugin) AddPerfData(pd *perfdata.PerfData) {
_, exists := p.perfData[pd.Label] _, exists := p.perfData[pd.Label]
if exists { if exists {
panic(fmt.Sprintf("duplicate performance data %s", pd.Label)) panic("duplicate performance data " + pd.Label)
} }
p.perfData[pd.Label] = pd p.perfData[pd.Label] = pd
} }
@ -87,30 +92,31 @@ func (p *Plugin) AddPerfData(pd *perfdata.PerfData) {
// and performance data, before exiting with the code corresponding to the // and performance data, before exiting with the code corresponding to the
// status. // status.
func (p *Plugin) Done() { func (p *Plugin) Done() {
var sb strings.Builder var strBuilder strings.Builder
sb.WriteString(p.name) strBuilder.WriteString(p.name)
sb.WriteString(" ") strBuilder.WriteString(" ")
sb.WriteString(p.status.String()) strBuilder.WriteString(p.status.String())
sb.WriteString(": ") strBuilder.WriteString(": ")
sb.WriteString(p.message) strBuilder.WriteString(p.message)
if len(p.perfData) > 0 { if len(p.perfData) > 0 {
sb.WriteString(" | ") strBuilder.WriteString(" | ")
needSep := false needSep := false
for k := range p.perfData { for _, data := range p.perfData {
if needSep { if needSep {
sb.WriteString(", ") strBuilder.WriteString(", ")
} else { } else {
needSep = true needSep = true
} }
sb.WriteString(p.perfData[k].String()) strBuilder.WriteString(data.String())
} }
} }
if p.extraText != nil { if p.extraText != nil {
for em := p.extraText.Front(); em != nil; em = em.Next() { for em := p.extraText.Front(); em != nil; em = em.Next() {
sb.WriteString("\n") strBuilder.WriteString("\n")
sb.WriteString(em.Value.(string)) //nolint:forcetypeassert // we want to panic if this isn't a string
strBuilder.WriteString(em.Value.(string))
} }
} }
fmt.Println(sb.String()) fmt.Println(strBuilder.String())
os.Exit(int(p.status)) os.Exit(int(p.status))
} }

View file

@ -6,4 +6,4 @@ type Program interface {
Done() Done()
} }
type ProgramBuilder func() Program type Builder func() Program