From 96637019c13e818ba2391a5fdc7708501b2bbb20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20BENO=C3=8ET?= Date: Fri, 19 Jul 2024 14:29:51 +0200 Subject: [PATCH] refactor: change the code's whole structure --- .forgejo/workflows/build.yaml | 7 +- Makefile | 58 ++++++++++ build.sh | 15 --- .../main.go | 23 ++-- cmd/{check_zone_serial => zoneserial}/main.go | 23 ++-- go.mod | 10 +- go.sum | 6 + main.go | 55 +++++++++ {perfdata => pkg/perfdata}/perfdata.go | 2 +- pkg/perfdata/perfdata_test.go | 108 ++++++++++++++++++ {plugin => pkg/plugin}/plugin.go | 4 +- pkg/program/program.go | 9 ++ pkg/version/version.go | 27 +++++ 13 files changed, 295 insertions(+), 52 deletions(-) create mode 100644 Makefile delete mode 100755 build.sh rename cmd/{check_ssl_certificate => sslcert}/main.go (96%) rename cmd/{check_zone_serial => zoneserial}/main.go (94%) create mode 100644 main.go rename {perfdata => pkg/perfdata}/perfdata.go (98%) create mode 100644 pkg/perfdata/perfdata_test.go rename {plugin => pkg/plugin}/plugin.go (96%) create mode 100644 pkg/program/program.go create mode 100644 pkg/version/version.go diff --git a/.forgejo/workflows/build.yaml b/.forgejo/workflows/build.yaml index 5d537ce..a8c2f24 100644 --- a/.forgejo/workflows/build.yaml +++ b/.forgejo/workflows/build.yaml @@ -25,5 +25,8 @@ jobs: - name: Build plugins run: | - go mod download - ./build.sh + make build-cross + + - name: Run tests + run: | + make test diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9bbe2e5 --- /dev/null +++ b/Makefile @@ -0,0 +1,58 @@ +BINNAME = go-monitoring +BINDIR = $(CURDIR)/bin +TARGETS = linux/amd64 linux/386 linux/arm linux/arm64 + +SRC := $(shell find . -type f -name '*.go' -print) go.mod go.sum +GOHOSTOS = $(shell go env GOHOSTOS) +GOHOSTARCH ?= $(shell go env GOHOSTARCH) + +GIT_TAG = $(shell git describe --tags --abbrev=0 --exact-match 2>/dev/null) +GIT_COMMIT = $(shell git rev-parse --short HEAD 2>/dev/null) +GIT_STATUS = $(shell test -n "`git status --porcelain`" && echo "dirty" || echo "clean") + +ifneq ($(VERSION),) + BINARY_VERSION = $(VERSION) +endif +BINARY_VERSION ?= $(GIT_TAG) + +LDFLAGS += -w -s +ifneq ($(BINARY_VERSION),) + LDFLAGS += -X nocternity.net/go-monitoring/pkg/version.version=${BINARY_VERSION} +endif +LDFLAGS += -X nocternity.net/go-monitoring/pkg/version.commit=${GIT_COMMIT} +LDFLAGS += -X nocternity.net/go-monitoring/pkg/version.status=${GIT_STATUS} + + +.PHONY: all +all: build + +.PHONY: build +build: GOOS=$(GOHOSTOS) +build: GOARCH=$(GOHOSTARCH) +build: build-target symlink + +.PHONY: symlink +symlink: + @ln -sf $(BINDIR)/$(GOOS)/$(GOARCH)/$(BINNAME) $(BINDIR)/$(BINNAME) + +.PHONY: build-target +build-target: LDFLAGS += -X nocternity.net/go-monitoring/pkg/version.target=${GOOS}/${GOARCH} +build-target: $(BINDIR)/$(GOOS)/$(GOARCH)/$(BINNAME) + +$(BINDIR)/$(GOOS)/$(GOARCH)/$(BINNAME): $(SRC) + @mkdir -p $(BINDIR)/$(GOOS)/$(GOARCH) + @CGO_ENABLED=0 go build -ldflags "$(LDFLAGS)" -o $(BINDIR)/$(GOOS)/$(GOARCH)/$(BINNAME) . + +.PHONY: test +test: build + @go test -v ./... + +.PHONY: build-cross +build-cross: + @for target in $(TARGETS); do \ + $(MAKE) build-target GOOS=`echo $$target | cut -d / -f 1` GOARCH=`echo $$target | cut -d / -f 2`; \ + done + +.PHONY: clean +clean: + @rm -rf $(BINDIR) diff --git a/build.sh b/build.sh deleted file mode 100755 index f08d025..0000000 --- a/build.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -set -e -cd "$(dirname $0)" -for arch in 386 amd64; do - mkdir -p bin/$arch - for d in $(find cmd -mindepth 1 -maxdepth 1 -type d); do - pushd $d >/dev/null - xn="$(basename "$d")" - GOARCH=$arch go get . - GOARCH=$arch go build - /bin/mv "$xn" ../../bin/$arch - popd >/dev/null - done -done diff --git a/cmd/check_ssl_certificate/main.go b/cmd/sslcert/main.go similarity index 96% rename from cmd/check_ssl_certificate/main.go rename to cmd/sslcert/main.go index facee9f..fefccf4 100644 --- a/cmd/check_ssl_certificate/main.go +++ b/cmd/sslcert/main.go @@ -1,4 +1,4 @@ -package main +package sslcert // import nocternity.net/go-monitoring/cmd/sslcert import ( "bufio" @@ -12,8 +12,9 @@ import ( "strings" "time" - "nocternity.net/go/monitoring/perfdata" - "nocternity.net/go/monitoring/plugin" + "nocternity.net/go-monitoring/pkg/perfdata" + "nocternity.net/go-monitoring/pkg/plugin" + "nocternity.net/go-monitoring/pkg/program" "github.com/karrick/golf" ) @@ -198,7 +199,7 @@ func (flags *programFlags) parseArguments() { } // Initialise the monitoring check program. -func newProgram() *checkProgram { +func NewProgram() program.Program { program := &checkProgram{ plugin: plugin.New("Certificate check"), } @@ -207,13 +208,13 @@ func newProgram() *checkProgram { } // Terminate the monitoring check program. -func (program *checkProgram) close() { +func (program *checkProgram) Done() { program.plugin.Done() } // Check the values that were specified from the command line. Returns true // if the arguments made sense. -func (program *checkProgram) checkFlags() bool { +func (program *checkProgram) CheckArguments() bool { if program.hostname == "" { program.plugin.SetState(plugin.UNKNOWN, "no hostname specified") return false @@ -332,7 +333,7 @@ func (program *checkProgram) setPerfData(tlDays int) { // Run the check: fetch the certificate, check its names then check its time // to expiry and update the plugin's performance data. -func (program *checkProgram) runCheck() { +func (program *checkProgram) RunCheck() { err := program.getCertificate() if err != nil { program.plugin.SetState(plugin.UNKNOWN, err.Error()) @@ -343,11 +344,3 @@ func (program *checkProgram) runCheck() { program.setPerfData(tlDays) } } - -func main() { - program := newProgram() - defer program.close() - if program.checkFlags() { - program.runCheck() - } -} diff --git a/cmd/check_zone_serial/main.go b/cmd/zoneserial/main.go similarity index 94% rename from cmd/check_zone_serial/main.go rename to cmd/zoneserial/main.go index c956ff6..af78d50 100644 --- a/cmd/check_zone_serial/main.go +++ b/cmd/zoneserial/main.go @@ -1,4 +1,4 @@ -package main +package zoneserial // import nocternity.net/go-monitoring/cmd/zoneserial import ( "fmt" @@ -8,8 +8,9 @@ import ( "strings" "time" - "nocternity.net/go/monitoring/perfdata" - "nocternity.net/go/monitoring/plugin" + "nocternity.net/go-monitoring/pkg/perfdata" + "nocternity.net/go-monitoring/pkg/plugin" + "nocternity.net/go-monitoring/pkg/program" "github.com/karrick/golf" "github.com/miekg/dns" @@ -75,7 +76,7 @@ func (flags *programFlags) parseArguments() { } // Initialise the monitoring check program. -func newProgram() *checkProgram { +func NewProgram() program.Program { program := &checkProgram{ plugin: plugin.New("DNS zone serial match check"), } @@ -84,7 +85,7 @@ func newProgram() *checkProgram { } // Terminate the monitoring check program. -func (program *checkProgram) close() { +func (program *checkProgram) Done() { if r := recover(); r != nil { program.plugin.SetState(plugin.UNKNOWN, "Internal error") program.plugin.AddLine("Error info: %v", r) @@ -93,7 +94,7 @@ func (program *checkProgram) close() { } // Check the values that were specified from the command line. Returns true if the arguments made sense. -func (program *checkProgram) checkFlags() bool { +func (program *checkProgram) CheckArguments() bool { if program.hostname == "" { program.plugin.SetState(plugin.UNKNOWN, "no DNS hostname specified") return false @@ -176,7 +177,7 @@ func (program *checkProgram) getSerial(server string, response queryResponse) (o // Run the monitoring check. This implies querying both servers, extracting the serial from // their responses, then comparing the serials. -func (program *checkProgram) runCheck() { +func (program *checkProgram) RunCheck() { checkResponse, refResponse := program.queryServers() cOk, cSerial := program.getSerial("checked", checkResponse) rOk, rSerial := program.getSerial("reference", refResponse) @@ -190,11 +191,3 @@ func (program *checkProgram) runCheck() { program.plugin.SetState(plugin.CRITICAL, "serials mismatch") } } - -func main() { - program := newProgram() - defer program.close() - if program.checkFlags() { - program.runCheck() - } -} diff --git a/go.mod b/go.mod index 207e862..b9ffb1a 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,14 @@ -module nocternity.net/go/monitoring +module nocternity.net/go-monitoring -go 1.15 +go 1.22 require ( github.com/karrick/golf v1.4.0 github.com/miekg/dns v1.1.40 ) + +require ( + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 // indirect + golang.org/x/net v0.0.0-20190923162816-aa69164e4478 // indirect + golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe // indirect +) diff --git a/go.sum b/go.sum index 55b350b..17c7bb2 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,20 @@ +github.com/karrick/golf v1.4.0 h1:9i9HnUh7uCyUFJhIqg311HBibw4f2pbGldi0ZM2FhaQ= github.com/karrick/golf v1.4.0/go.mod h1:qGN0IhcEL+IEgCXp00RvH32UP59vtwc8w5YcIdArNRk= +github.com/miekg/dns v1.1.40 h1:pyyPFfGMnciYUk/mXpKkVmeMQjfXqt3FAJ2hy7tPiLA= github.com/miekg/dns v1.1.40/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= diff --git a/main.go b/main.go new file mode 100644 index 0000000..dff10ed --- /dev/null +++ b/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + + "nocternity.net/go-monitoring/cmd/sslcert" + "nocternity.net/go-monitoring/cmd/zoneserial" + "nocternity.net/go-monitoring/pkg/program" + "nocternity.net/go-monitoring/pkg/version" +) + +var ( + programs map[string]program.ProgramBuilder = map[string]program.ProgramBuilder{ + "check_ssl_certificate": sslcert.NewProgram, + "check_zone_serial": zoneserial.NewProgram, + } +) + +func exitError(msg string, args ...interface{}) { + fmt.Fprintf(os.Stderr, msg+"\n", args...) + os.Exit(1) +} + +func main() { + ownName := filepath.Base(os.Args[0]) + + var program program.Program + if builder, ok := programs[ownName]; ok { + program = builder() + } else if len(os.Args) < 2 { + exitError("Syntax: %s [arguments]\n %s programs", os.Args[0], os.Args[0]) + } else if os.Args[1] == "programs" { + fmt.Println("Available programs:") + for name := range programs { + fmt.Printf(" %s\n", name) + } + os.Exit(0) + } else if os.Args[1] == "--version" || os.Args[1] == "-v" { + fmt.Printf("%s %s\n", ownName, version.Version()) + os.Exit(0) + + } else if builder, ok := programs[os.Args[1]]; ok { + os.Args = os.Args[1:] + program = builder() + } else { + exitError("Unknown program: %s", os.Args[1]) + } + + defer program.Done() + if program.CheckArguments() { + program.RunCheck() + } +} diff --git a/perfdata/perfdata.go b/pkg/perfdata/perfdata.go similarity index 98% rename from perfdata/perfdata.go rename to pkg/perfdata/perfdata.go index d4805ad..1456831 100644 --- a/perfdata/perfdata.go +++ b/pkg/perfdata/perfdata.go @@ -1,6 +1,6 @@ // Package `perfdata` provides representations for a monitoring plugin's // performance data. -package perfdata +package perfdata // import nocternity.net/go-monitoring/pkg/perfdata import ( "fmt" diff --git a/pkg/perfdata/perfdata_test.go b/pkg/perfdata/perfdata_test.go new file mode 100644 index 0000000..473be57 --- /dev/null +++ b/pkg/perfdata/perfdata_test.go @@ -0,0 +1,108 @@ +package perfdata // import nocternity.net/go-monitoring/pkg/perfdata + +import ( + "fmt" + "testing" +) + +func assert(t *testing.T, check bool, msg string) { + if !check { + t.Errorf(msg) + } +} + +func assertPanic(t *testing.T, f func(), msg string) { + defer func() { + if r := recover(); r == nil { + t.Errorf(msg) + } + }() + f() +} + +func TestValueCheckValid(t *testing.T) { + validValues := []string{ + "0", "0.", "0.952", "1", "123", "123.", "123.45", ".1", + "-0", "-0.", "-0.952", "-1", "-123", "-123.", "-123.45", "-.1", + } + + for _, value := range validValues { + if !valueCheck.MatchString(value) { + t.Errorf("'%s' is a valid value string", value) + } + } +} + +func TestValueCheckInvalid(t *testing.T) { + invalidValues := []string{".", "-.", "a", " ", ""} + + for _, value := range invalidValues { + if valueCheck.MatchString(value) { + t.Errorf("'%s' is an invalid value string", value) + } + } +} + +func TestPdrMaxInvalid(t *testing.T) { + assertPanic( + t, func() { PDRMax("") }, + "Created PerfDataRange with invalid max value", + ) +} + +func TestPdrMax(t *testing.T) { + value := "123" + pdr := PDRMax(value) + assert(t, pdr.start == "0", "Min value should be '0'") + assert(t, pdr.end == value, "Max value not copied to PerfDataRange") + assert(t, !pdr.inside, "Inside flag should not be set") +} + +func TestPdrMinMaxInvalid(t *testing.T) { + assertPanic( + t, func() { PDRMinMax("", "123") }, + "Created PerfDataRange with invalid min value", + ) + assertPanic( + t, func() { PDRMinMax("123", "") }, + "Created PerfDataRange with invalid max value", + ) +} + +func TestPdrMinMax(t *testing.T) { + min, max := "123", "456" + pdr := PDRMinMax(min, max) + assert(t, pdr.start == min, "Min value not copied to PerfDataRange") + assert(t, pdr.end == max, "Max value not copied to PerfDataRange") + assert(t, !pdr.inside, "Inside flag should not be set") +} + +func TestPdrInside(t *testing.T) { + pdr := &PerfDataRange{} + pdr = pdr.Inside() + assert(t, pdr.inside, "Inside flag should be set") + pdr = pdr.Inside() + assert(t, pdr.inside, "Inside flag should still be set") +} + +func TestPdrString(t *testing.T) { + type Test struct { + pdr PerfDataRange + out string + } + tests := []Test{ + {pdr: PerfDataRange{start: "Y", end: "X"}, out: "Y:X"}, + {pdr: PerfDataRange{end: "X"}, out: "~:X"}, + {pdr: PerfDataRange{start: "0", end: "X"}, out: ":X"}, + {pdr: PerfDataRange{inside: true, start: "Y", end: "X"}, out: "@Y:X"}, + } + + for _, test := range tests { + result := test.pdr.String() + assert( + t, + result == test.out, + fmt.Sprintf("Expected '%s', got '%s'", test.out, result), + ) + } +} diff --git a/plugin/plugin.go b/pkg/plugin/plugin.go similarity index 96% rename from plugin/plugin.go rename to pkg/plugin/plugin.go index eee5be9..e4206ba 100644 --- a/plugin/plugin.go +++ b/pkg/plugin/plugin.go @@ -1,6 +1,6 @@ // Package plugin implements a helper that can be used to implement a Nagios, // Centreon, Icinga... service monitoring plugin. -package plugin +package plugin // import nocternity.net/go-monitoring/pkg/perfdata import ( "container/list" @@ -8,7 +8,7 @@ import ( "os" "strings" - "nocternity.net/go/monitoring/perfdata" + "nocternity.net/go-monitoring/pkg/perfdata" ) // Status represents the return status of the monitoring plugin. The diff --git a/pkg/program/program.go b/pkg/program/program.go new file mode 100644 index 0000000..7f2ec06 --- /dev/null +++ b/pkg/program/program.go @@ -0,0 +1,9 @@ +package program // import nocternity.net/go-monitoring/pkg/program + +type Program interface { + CheckArguments() bool + RunCheck() + Done() +} + +type ProgramBuilder func() Program diff --git a/pkg/version/version.go b/pkg/version/version.go new file mode 100644 index 0000000..cc49dcf --- /dev/null +++ b/pkg/version/version.go @@ -0,0 +1,27 @@ +package version // import nocternity.net/go-monitoring/pkg/version + +import "fmt" + +var ( + version = "" + commit = "unknown" + status = "" + target = "unknown/unknown" +) + +func Version() string { + var schar, ver string + if status == "dirty" { + schar = "*" + } else { + schar = "" + } + + if version == "" { + ver = "development version" + } else { + ver = version + } + + return fmt.Sprintf("%s (%s%s) %s", ver, commit, schar, target) +}