From 3a1e1fe64d172459f6e990dae165754b24b35b83 Mon Sep 17 00:00:00 2001 From: Vladimir Ermakov Date: Fri, 3 Apr 2020 21:19:36 +0300 Subject: [PATCH] dbus: fix support for CentOS 7 by using introspection Fix extracted from: https://github.com/elastic/beats/pull/16902 --- dbus.go | 182 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 3 +- go.sum | 1 + main.go | 54 ++++++----------- 4 files changed, 202 insertions(+), 38 deletions(-) create mode 100644 dbus.go diff --git a/dbus.go b/dbus.go new file mode 100644 index 0000000..6a6e3f1 --- /dev/null +++ b/dbus.go @@ -0,0 +1,182 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +//+build !netbsd + +package main + +import ( + "encoding/xml" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/coreos/go-systemd/v22/dbus" + dbusRaw "github.com/godbus/dbus" + "github.com/pkg/errors" +) + +type unitFetcher func(conn *dbus.Conn, states, patterns []string) ([]dbus.UnitStatus, error) + +// instrospectForUnitMethods determines what methods are available via dbus for listing systemd units. +// We have a number of functions, some better than others, for getting and filtering unit lists. +// This will attempt to find the most optimal method, and move down to methods that require more work. +func instrospectForUnitMethods() (unitFetcher, error) { + //setup a dbus connection + conn, err := dbusRaw.SystemBusPrivate() + if err != nil { + return nil, errors.Wrap(err, "error getting connection to system bus") + } + + auth := dbusRaw.AuthExternal(strconv.Itoa(os.Getuid())) + err = conn.Auth([]dbusRaw.Auth{auth}) + if err != nil { + return nil, errors.Wrap(err, "error authenticating") + } + + err = conn.Hello() + if err != nil { + return nil, errors.Wrap(err, "error in Hello") + } + + var props string + + //call "introspect" on the systemd1 path to see what ListUnit* methods are available + obj := conn.Object("org.freedesktop.systemd1", dbusRaw.ObjectPath("/org/freedesktop/systemd1")) + err = obj.Call("org.freedesktop.DBus.Introspectable.Introspect", 0).Store(&props) + if err != nil { + return nil, errors.Wrap(err, "error calling dbus") + } + + unitMap, err := parseXMLAndReturnMethods(props) + if err != nil { + return nil, errors.Wrap(err, "error handling XML") + } + + //return a function callback ordered by desirability + if _, ok := unitMap["ListUnitsByPatterns"]; ok { + return listUnitsByPatternWrapper, nil + } else if _, ok := unitMap["ListUnitsFiltered"]; ok { + return listUnitsFilteredWrapper, nil + } else if _, ok := unitMap["ListUnits"]; ok { + return listUnitsWrapper, nil + } + return nil, fmt.Errorf("no supported list Units function: %v", unitMap) +} + +func parseXMLAndReturnMethods(str string) (map[string]bool, error) { + + type Method struct { + Name string `xml:"name,attr"` + } + + type Iface struct { + Name string `xml:"name,attr"` + Method []Method `xml:"method"` + } + + type IntrospectData struct { + XMLName xml.Name `xml:"node"` + Interface []Iface `xml:"interface"` + } + + methods := IntrospectData{} + + err := xml.Unmarshal([]byte(str), &methods) + if err != nil { + return nil, errors.Wrap(err, "error unmarshalling XML") + } + + if len(methods.Interface) == 0 { + return nil, errors.Wrap(err, "no methods found on introspect") + } + methodMap := make(map[string]bool) + for _, iface := range methods.Interface { + for _, method := range iface.Method { + if strings.Contains(method.Name, "ListUnits") { + methodMap[method.Name] = true + } + } + } + + return methodMap, nil +} + +// listUnitsByPatternWrapper is a bare wrapper for the unitFetcher type +func listUnitsByPatternWrapper(conn *dbus.Conn, states, patterns []string) ([]dbus.UnitStatus, error) { + return conn.ListUnitsByPatterns(states, patterns) +} + +//listUnitsFilteredWrapper wraps the dbus ListUnitsFiltered method +func listUnitsFilteredWrapper(conn *dbus.Conn, states, patterns []string) ([]dbus.UnitStatus, error) { + units, err := conn.ListUnitsFiltered(states) + if err != nil { + return nil, errors.Wrap(err, "ListUnitsFiltered error") + } + + return matchUnitPatterns(patterns, units) +} + +// listUnitsWrapper wraps the dbus ListUnits method +func listUnitsWrapper(conn *dbus.Conn, states, patterns []string) ([]dbus.UnitStatus, error) { + units, err := conn.ListUnits() + if err != nil { + return nil, errors.Wrap(err, "ListUnits error") + } + if len(patterns) > 0 { + units, err = matchUnitPatterns(patterns, units) + if err != nil { + return nil, errors.Wrap(err, "error matching unit patterns") + } + } + + if len(states) > 0 { + var finalUnits []dbus.UnitStatus + for _, unit := range units { + for _, state := range states { + if unit.LoadState == state || unit.ActiveState == state || unit.SubState == state { + finalUnits = append(finalUnits, unit) + break + } + } + } + return finalUnits, nil + } + + return units, nil +} + +// matchUnitPatterns returns a list of units that match the pattern list. +// This algo, including filepath.Match, is designed to (somewhat) emulate the behavior of ListUnitsByPatterns, which uses `fnmatch`. +func matchUnitPatterns(patterns []string, units []dbus.UnitStatus) ([]dbus.UnitStatus, error) { + var matchUnits []dbus.UnitStatus + for _, unit := range units { + for _, pattern := range patterns { + match, err := filepath.Match(pattern, unit.Name) + if err != nil { + return nil, errors.Wrapf(err, "error matching with pattern %s", pattern) + } + if match { + matchUnits = append(matchUnits, unit) + break + } + } + } + return matchUnits, nil +} diff --git a/go.mod b/go.mod index 695e053..f24a851 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,11 @@ require ( github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect github.com/coreos/go-systemd/v22 v22.0.0 github.com/fsnotify/fsnotify v1.4.9 // indirect - github.com/godbus/dbus v4.1.0+incompatible // indirect + github.com/godbus/dbus v4.1.0+incompatible github.com/golang/protobuf v1.3.5 // indirect github.com/mitchellh/mapstructure v1.2.2 // indirect github.com/pelletier/go-toml v1.7.0 // indirect + github.com/pkg/errors v0.8.1 github.com/robertkrimen/otto v0.0.0-20191219234010-c382bd3c16ff // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/sensu-community/sensu-plugin-sdk v0.6.0 diff --git a/go.sum b/go.sum index 8c8f75a..6df0e4e 100644 --- a/go.sum +++ b/go.sum @@ -186,6 +186,7 @@ github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAv github.com/pierrec/cmdflag v0.0.2/go.mod h1:a3zKGZ3cdQUfxjd0RGMLZr8xI3nvpJOB+m6o/1X5BmU= github.com/pierrec/lz4/v3 v3.0.1/go.mod h1:280XNCGS8jAcG++AHdd6SeWnzyJ1w9oow2vbORyey8Q= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/main.go b/main.go index c455141..24e9b42 100644 --- a/main.go +++ b/main.go @@ -12,8 +12,7 @@ import ( // Config represents the check plugin config. type Config struct { sensu.PluginConfig - Units []string - UseByNameSearch bool + UnitPatterns []string } var ( @@ -31,17 +30,8 @@ var ( Env: "SYSTEMD_UNIT", Argument: "unit", Shorthand: "s", - Usage: "Systemd unit(s) to check", - Value: &plugin.Units, - }, - &sensu.PluginConfigOption{ - Path: "use_by_name_search", - Env: "SYSTEMD_USE_BY_NAME_SEARCH", - Argument: "by-name", - Shorthand: "n", - Usage: "use by name search (use with systemd > 230)", - Value: &plugin.UseByNameSearch, - Default: false, + Usage: "Systemd unit(s) pattern to check", + Value: &plugin.UnitPatterns, }, } ) @@ -51,18 +41,8 @@ func main() { check.Execute() } -func ssContains(s string, ss []string) bool { - for _, sl := range ss { - if s == sl { - return true - } - } - - return false -} - func checkArgs(event *types.Event) (int, error) { - if len(plugin.Units) == 0 { + if len(plugin.UnitPatterns) == 0 { return sensu.CheckStateWarning, fmt.Errorf("--unit or SYSTEMD_UNIT environment variable is required") } @@ -76,25 +56,25 @@ func executeCheck(event *types.Event) (int, error) { } defer conn.Close() - var unitStats []dbus.UnitStatus - - if plugin.UseByNameSearch { - unitStats, err = conn.ListUnitsByNames(plugin.Units) - } else { - unitStats, err = conn.ListUnitsByPatterns(nil, plugin.Units) + unitFetcher, err := instrospectForUnitMethods() + if err != nil { + return sensu.CheckStateUnknown, fmt.Errorf("could not introspect systemd dbus: %w", err) } + + unitStats, err := unitFetcher(conn, nil, plugin.UnitPatterns) if err != nil { return sensu.CheckStateUnknown, fmt.Errorf("list units error: %w", err) } - if len(unitStats) < len(plugin.Units) { + if len(unitStats) < len(plugin.UnitPatterns) { err = nil - foundUnits := make([]string, 0, len(unitStats)) - for _, unit := range unitStats { - foundUnits = append(foundUnits, unit.Name) - } - for _, unit := range plugin.Units { - if !ssContains(unit, foundUnits) { + for _, unit := range plugin.UnitPatterns { + matched, err := matchUnitPatterns([]string{unit}, unitStats) + if err != nil { + fmt.Printf("CRITICAL: %s: match error: %v\n", unit, err) + err = multierr.Append(err, fmt.Errorf("%s: match error: %w", unit, err)) + } + if len(matched) == 0 { fmt.Printf("CRITICAL: %s: not present\n", unit) err = multierr.Append(err, fmt.Errorf("%s: not present", unit)) }