refactor: reorganize project in order to include automation #1
7 changed files with 191 additions and 163 deletions
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
53
main.go
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,4 +6,4 @@ type Program interface {
|
||||||
Done()
|
Done()
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProgramBuilder func() Program
|
type Builder func() Program
|
||||||
|
|
Loading…
Reference in a new issue