Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions docs/checks/commands/check_temperature.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,13 @@ Naemon Config

these can be used in filters and thresholds (along with the default attributes):

| Attribute | Description |
| --------- | ---------------------------------------------- |
| sensor | full name of this sensor, ex.: coretemp_core_0 |
| name | name of this sensor, ex.: coretemp |
| label | label for this sensor, ex.: core 0 |
| value | current temperature |
| crit | critical value supplied from sensor |
| max | max value supplied from sensor |
| min | min value supplied from sensor |
| Attribute | Description |
| ----------- | ------------------------------------------------------------------------------------------------------- |
| sensor | full name of this sensor, ex.: coretemp_core_0 |
| name | name of this sensor, ex.: coretemp |
| label | label for this sensor, ex.: core 0 |
| value | current temperature |
| crit | critical value supplied from sensor |
| max | max value supplied from sensor |
| min | min value supplied from sensor |
| temperature | raw temperature value. this can be a wrong/misleading value like 0 or 1 if sensor is disabled. use 'value' attribute instead. |
6 changes: 6 additions & 0 deletions pkg/snclient/check_drivesize.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"maps"
"regexp"
"runtime"
"sort"
"strconv"
Expand Down Expand Up @@ -154,6 +155,11 @@ func (l *CheckDrivesize) Build() *CheckData {
{name: "localised_remote_path", description: "Windows only: If the path is given as a remote path, and that remote path has an assigned logical drive," +
" this is the replaced path under that logical drive."},
},
extraFilterAttributes: []*regexp.Regexp{
regexp.MustCompile(`^flags$`),
regexp.MustCompile(`^letter$`),
regexp.MustCompile(`^opts$`),
},
exampleDefault: l.getExample(),
exampleArgs: `'warn=used_pct > 90' 'crit=used_pct > 95'`,
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/snclient/check_logfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ func (c *CheckLogFile) Build() *CheckData {
{name: "line", description: "Match the content of an entire line"},
{name: "columnN", description: "Match the content of the N-th column only if enough columns exists"},
},
extraFilterAttributes: []*regexp.Regexp{
regexp.MustCompile(`^column.*`),
},
exampleDefault: `
Alert if there are errors in the snclient log file:

Expand Down
7 changes: 7 additions & 0 deletions pkg/snclient/check_memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package snclient
import (
"context"
"fmt"
"regexp"
"runtime"

"github.com/consol-monitoring/snclient/pkg/humanize"
Expand Down Expand Up @@ -76,6 +77,12 @@ read more on windows virtual address space:
{name: "size", description: "Total memory in human readable bytes (IEC)", unit: UByte},
{name: "size_bytes", description: "Total memory in bytes (IEC)", unit: UByte},
},
extraFilterAttributes: []*regexp.Regexp{
regexp.MustCompile(`^(?i)physical$`),
regexp.MustCompile(`^(?i)swap$`),
regexp.MustCompile(`^(?i)committed$`),
regexp.MustCompile(`^(?i)virtual$`),
},
exampleDefault: `
check_memory
OK - physical = 6.98 GiB, committed = 719.32 MiB|...
Expand Down
7 changes: 7 additions & 0 deletions pkg/snclient/check_os_updates.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"cmp"
"context"
"fmt"
"regexp"
"slices"

"github.com/consol-monitoring/snclient/pkg/convert"
Expand Down Expand Up @@ -49,6 +50,12 @@ func (l *CheckOSUpdates) Build() *CheckData {
{name: "security", description: "is this a security update: 0 / 1"},
{name: "version", description: "version string of package"},
},
extraFilterAttributes: []*regexp.Regexp{
regexp.MustCompile(`^old_version$`),
regexp.MustCompile(`^repository$`),
regexp.MustCompile(`^arch$`),
regexp.MustCompile(`^prefix$`),
},
exampleDefault: `
check_os_updates
OK - no updates available |...
Expand Down
4 changes: 4 additions & 0 deletions pkg/snclient/check_pdh.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package snclient

import (
"context"
"regexp"
)

func init() {
Expand Down Expand Up @@ -48,6 +49,9 @@ func (c *CheckPDH) Build() *CheckData {
{name: "count ", description: "Number of items matching the filter. Common option for all checks."},
{name: "value ", description: "The counter value (either float or int)"},
},
extraFilterAttributes: []*regexp.Regexp{
regexp.MustCompile(`^name$`),
},
exampleDefault: `
check_pdh "counter=foo" "warn=value > 80" "crit=value > 90"
Everything looks good
Expand Down
2 changes: 2 additions & 0 deletions pkg/snclient/check_temperature.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ func (l *CheckTemperature) Build() *CheckData {
{name: "crit", description: "critical value supplied from sensor"},
{name: "max", description: "max value supplied from sensor"},
{name: "min", description: "min value supplied from sensor"},
{name: "temperature", description: "raw temperature value." +
" this can be a wrong/misleading value like 0 or 1 if sensor is disabled. use 'value' attribute instead."},
},
listSorted: []string{"label"},
exampleDefault: `
Expand Down
4 changes: 4 additions & 0 deletions pkg/snclient/check_wmi.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package snclient
import (
"context"
"fmt"
"regexp"
"runtime"
"strings"

Expand Down Expand Up @@ -49,6 +50,9 @@ func (l *CheckWMI) Build() *CheckData {
hasArgsFilter: true, // otherwise empty-syntax won't be applied
topSyntax: "${list}",
detailSyntax: "%(line)",
extraFilterAttributes: []*regexp.Regexp{
regexp.MustCompile(`.*`),
},
exampleDefault: `
check_wmi "query=select DeviceID, FreeSpace FROM Win32_LogicalDisk WHERE DeviceID = 'C:'"
C:, 27955118080
Expand Down
109 changes: 88 additions & 21 deletions pkg/snclient/checkdata.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package snclient
import (
"fmt"
"math"
"regexp"
"slices"
"sort"
"strconv"
Expand Down Expand Up @@ -137,6 +138,7 @@ type CheckData struct {
output OutputMode
implemented Implemented
attributes []CheckAttribute
extraFilterAttributes []*regexp.Regexp
listSorted []string // sort result list by this keys
exampleDefault string
exampleArgs string
Expand Down Expand Up @@ -858,6 +860,83 @@ func (cd *CheckData) preParseArgs(args []string) (sanitized []Argument, defaultW
return sanitized, defaultWarning, defaultCritical, applyDefaultFilter, nil
}

// Threshold keywords do not necessarily have to match an attribute name.
// They can be written to match perfdata metric names
// They can also be match to check details, but that is less common
// Therefore, this function does not return an error
func (cd *CheckData) checkThresholdKeywordsAgainstAttributeNames() {
attributeNames := cd.getAttributeNames()

warningKeywords, err := cd.warnThreshold.GetListOfKeywords()
if err == nil && len(warningKeywords) > 0 {
warningKeywordsExtra := utils.SubtractSlice(warningKeywords, attributeNames)
if len(warningKeywordsExtra) > 0 {
log.Tracef("Warning condition uses keyword(s) not present in the attributes, run with --help to get a list of attributes, extra keywords: %v", warningKeywordsExtra)
}
}

critKeywords, err := cd.critThreshold.GetListOfKeywords()
if err == nil && len(critKeywords) > 0 {
critKeywordsExtra := utils.SubtractSlice(critKeywords, attributeNames)
if len(critKeywordsExtra) > 0 {
log.Tracef("Crit condition uses keyword(s) not present in the attributes, run with --help to get a list of attributes, extra keywords: %v", critKeywordsExtra)
}
}

okKeywords, err := cd.okThreshold.GetListOfKeywords()
if err == nil && len(okKeywords) > 0 {
okKeywordsExtra := utils.SubtractSlice(okKeywords, attributeNames)
if len(okKeywordsExtra) > 0 {
log.Tracef("Ok condition uses keyword(s) not present in the attributes, run with --help to get a list of attributes, extra keywords: %v", okKeywordsExtra)
}
}
}

// Filter keywords should match attribute filters.
// They are used to filter entries added to the check. Entries use attributes.
// They are not compared with perfdata metric names.
// They are not compared with check details.
// Not matching an attribute name means the condition has no effect, and is likely wrong
func (cd *CheckData) checkFilterKeywordsAgainstAttributeNames() (err error) {
attributeNames := cd.getAttributeNames()
regexes := make([]*regexp.Regexp, 0, len(attributeNames)+len(cd.extraFilterAttributes))

for _, attributeName := range attributeNames {
regexes = append(regexes, regexp.MustCompile(regexp.QuoteMeta(attributeName)))
}

regexes = append(regexes, cd.extraFilterAttributes...)

filterKeywords, err := cd.filter.GetListOfKeywords()

filterKeywordsUnmatched := []string{}
if err == nil && len(filterKeywords) > 0 {
for _, filterKeyword := range filterKeywords {
if !slices.ContainsFunc(regexes, func(r *regexp.Regexp) bool { return r.MatchString(filterKeyword) }) {
filterKeywordsUnmatched = append(filterKeywordsUnmatched, filterKeyword)
}
}
}

if len(filterKeywordsUnmatched) > 0 {
log.Warnf("Filter condition uses keyword(s) not present in the attribute, filter condition: '%s' , extra keywords: '%s' ", cd.filter.String(), strings.Join(filterKeywordsUnmatched, ", "))

return fmt.Errorf("filter condition uses unknown attribute '%s', run with --help to get a list of attributes", filterKeywordsUnmatched[0])
}

return nil
}

func (cd *CheckData) getAttributeNames() (attributeNames []string) {
attributeNames = make([]string, 0, len(cd.attributes))

for _, attribute := range cd.attributes {
attributeNames = append(attributeNames, attribute.name)
}

return attributeNames
}

func (cd *CheckData) fetchNextArg(args, split []string, keyword string, idx, numArgs int) (argVal string, newIdx int, err error) {
if len(split) == 2 {
return split[1], idx, nil
Expand Down Expand Up @@ -1122,15 +1201,18 @@ func (cd *CheckData) HasThreshold(name string) bool {

// GetAllThresholdKeywords returns a list of all keywords used in warn/crit/ok thresholds.
func (cd *CheckData) GetAllThresholdKeywords() []string {
keywords := []string{}
keywords := make([]string, 0, len(cd.warnThreshold)+len(cd.critThreshold)+len(cd.okThreshold))

keywords = append(keywords, cd.getAllThresholdKeywords(cd.warnThreshold)...)
keywords = append(keywords, cd.getAllThresholdKeywords(cd.critThreshold)...)
keywords = append(keywords, cd.getAllThresholdKeywords(cd.okThreshold)...)
warnThresholdKeywords, _ := cd.warnThreshold.GetListOfKeywords()
critThresholdKeywords, _ := cd.critThreshold.GetListOfKeywords()
okThresholdKeywords, _ := cd.okThreshold.GetListOfKeywords()

// make list unique
keywords = append(keywords, warnThresholdKeywords...)
keywords = append(keywords, critThresholdKeywords...)
keywords = append(keywords, okThresholdKeywords...)

utils.Deduplicate(keywords)
slices.Sort(keywords)
keywords = slices.Compact(keywords)

return keywords
}
Expand All @@ -1150,21 +1232,6 @@ func (cd *CheckData) hasThresholdCond(condList ConditionList, name string) bool
return false
}

// hasThresholdCond returns true is the given list of conditions uses the given name at least once.
func (cd *CheckData) getAllThresholdKeywords(condList ConditionList) []string {
keywords := []string{}

for _, cond := range condList {
if len(cond.group) > 0 {
keywords = append(keywords, cd.getAllThresholdKeywords(cond.group)...)
}

keywords = append(keywords, cond.keyword)
}

return keywords
}

// SetDefaultThresholdUnit sets default unit for all threshold conditions matching
// the name and not having a unit already
func (cd *CheckData) SetDefaultThresholdUnit(defaultUnit string, names []string) {
Expand Down
27 changes: 27 additions & 0 deletions pkg/snclient/checkdata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,30 @@ func TestAllRequiredMacros(t *testing.T) {
macros = check.AllRequiredMacros()
assert.Equal(t, []string{"blah", "column1", "column29", "test"}, macros)
}

func TestCheckingFilterKeywordsOnCheckAttributes(t *testing.T) {
check := &CheckData{
name: "testcheck",
description: "This test check simply has couple arguments.",
attributes: []CheckAttribute{
{
name: "attribute1",
description: "attribute1",
},
{
name: "attribute2",
description: "attribute2",
},
{
name: "attribute3",
description: "attribute3",
},
},
}

_, err := check.parseArgs([]string{"filter='(attribute1 eq value1) and (attribute2 like value2) and (attribute3 != value3)'", "filter='attribute4 eq value4'"})
require.NoError(t, err, "should not have a problem parsing arguments")

err = check.checkFilterKeywordsAgainstAttributeNames()
require.Error(t, err, "should error due to filter using keyword 'attribute4', which is not an attribute of check")
}
21 changes: 20 additions & 1 deletion pkg/snclient/condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -992,7 +992,9 @@ func (c *Condition) TransformMultipleKeywords(srcKeywords []string, targetKeywor
// recursively gets list of all keywords used in the condition
func (c *Condition) GetListOfKeywords() (keywords []string, err error) {
addKeywordToList := func(c *Condition) (err error) {
keywords = append(keywords, c.keyword)
if c.keyword != "" {
keywords = append(keywords, c.keyword)
}

return nil
}
Expand Down Expand Up @@ -1532,3 +1534,20 @@ func (cl *ConditionList) performMatches(data map[string]string, returnOnFirstMat

return correctConditions, falseConditions, conclusiveConditions, inconclusiveConditions
}

func (cl *ConditionList) GetListOfKeywords() (keywords []string, err error) {
keywords = make([]string, 0)

for _, cond := range *cl {
condKeywords, err := cond.GetListOfKeywords()
if err != nil {
return nil, fmt.Errorf("error gathering keywords: %s", err.Error())
}

keywords = append(keywords, condKeywords...)
}

keywords = utils.Deduplicate(keywords)

return keywords, nil
}
12 changes: 12 additions & 0 deletions pkg/snclient/snclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,7 @@ func (snc *Agent) runCheck(ctx context.Context, name string, args []string, time
if chk.showHelp > 0 {
return snc.runHelp(ctx, chk, handler), chk
}

if !skipAllowedCheck {
err = snc.checkAllowed(name, chk, handler, args, ArgumentList(parsedArgs).RawList(), transportConf)
if err != nil {
Expand All @@ -834,6 +835,17 @@ func (snc *Agent) runCheck(ctx context.Context, name string, args []string, time
}
}

if !chk.argsPassthrough {
chk.checkThresholdKeywordsAgainstAttributeNames()
err = chk.checkFilterKeywordsAgainstAttributeNames()
if err != nil {
return &CheckResult{
State: CheckExitUnknown,
Output: fmt.Sprintf("${status} - %s", err.Error()),
}, chk
}
}

if timeoutOverride > 0 {
chk.timeout = timeoutOverride
}
Expand Down