Skip to content

Commit

Permalink
Merge branch 'icinga-plugin'
Browse files Browse the repository at this point in the history
  • Loading branch information
XANi committed Dec 2, 2024
2 parents 0051163 + 3486c2e commit 9c6f179
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 0 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,23 @@ Example:
command: /path/to/i3blocks/volume
```

## Icinga

Icinga2 plugin. Icinga2 must have API enabled and served on given URL

Parameters:
```yaml
- name: icinga
plugin: icinga
config:
url: https://icinga/plugin/url
user: uber
pass: status_pass
# api_update_interval: 5m # defaults to 30s
# host_filter: icinga2 filter for hosts
# service_filter: icinga2 filter for services
```

## Uptime

System uptime. Parameters:
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ require (

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/efigence/go-icinga2 v0.2.0 // indirect
github.com/efigence/go-libs v0.0.3 // indirect
github.com/efigence/go-monitoring v0.0.3 // indirect
github.com/glycerine/blake2b v0.0.0-20151022103502-3c8c640cd7be // indirect
github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31 // indirect
github.com/glycerine/greenpack v5.1.1+incompatible // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eclipse/paho.mqtt.golang v1.2.0 h1:1F8mhG9+aO5/xpdtFkW4SxOJB67ukuDC3t2y2qayIX0=
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
github.com/efigence/go-icinga2 v0.2.0 h1:2TAQTBGnte3BG1I0oSoUm0g/THBRBUCxz1zG90u6pIs=
github.com/efigence/go-icinga2 v0.2.0/go.mod h1:4+KJI7Lq/21EpyyPr0kjkF9cxozpCSNWPh4GuPYKCxA=
github.com/efigence/go-libs v0.0.0-20171115061947-5b2936a0c2a1 h1:WJE5x8KYmcAW9U77NYb8WZBYvjjm9pjb7vS5TOePt7M=
github.com/efigence/go-libs v0.0.0-20171115061947-5b2936a0c2a1/go.mod h1:+L8bBUFAIfgv+GWyTdOiHNUw9M1jh/7KxzmYXK2pUQA=
github.com/efigence/go-libs v0.0.3 h1:lkvZAKB+bFWHYtvMm9nwbV8PBwBNQoRuoAbA1M8YPq0=
Expand All @@ -18,6 +20,8 @@ github.com/efigence/go-mon v0.1.1 h1:PqkjJatL9gslvh5kfuop9vG0VwTBcwIL8epRzfLQJwY
github.com/efigence/go-mon v0.1.1/go.mod h1:9RsOdOwCzexnFq0gqgi32gzj0jL3sfmz5gzU+lbhXx8=
github.com/efigence/go-mon v1.4.2 h1:oT9TBMfHS16drXOpkI6G0jIpjKTemO1BG4GVMqv7yWI=
github.com/efigence/go-mon v1.4.2/go.mod h1:7TP/HsWPDcoFOOPcEXwiknwAzSMGTGp0bvEuCH7froE=
github.com/efigence/go-monitoring v0.0.3 h1:HGROU4tGdtBgC7dP/As/x/Trb3cWMpiNiZrmKnE1wxo=
github.com/efigence/go-monitoring v0.0.3/go.mod h1:exrbNBDMWKiFIbppSFH4q+B+2dNYhDY0l+WxukXXOPY=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
Expand Down
216 changes: 216 additions & 0 deletions plugin/icinga/icinga.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
package icinga

import (
"fmt"
"github.com/XANi/uberstatus/config"
"github.com/XANi/uberstatus/uber"
"github.com/XANi/uberstatus/util"
"github.com/efigence/go-icinga2"
"github.com/efigence/go-mon"
"go.uber.org/zap"
"sync"

"time"
)

// Icinga2 monitoring

type pluginConfig struct {
Prefix string
Interval int
URL string `yaml:"url"`
User string `yaml:"user"`
Pass string `yaml:"pass"`
HostFilter string `yaml:"host_filter"`
ServiceFilter string `yaml:"service_filter"`
APIUpdateInterval time.Duration `yaml:"api_update_interval"`
}

type plugin struct {
l *zap.SugaredLogger
i *icinga2.API
cfg pluginConfig
hostStatusMap map[string]int
serviceStatusMap map[string]int
servicesDown []string
lastErr error
nextTs time.Time
sync.Mutex
}

func New(cfg uber.PluginConfig) (z uber.Plugin, err error) {
p := &plugin{
l: cfg.Logger,
}
p.cfg, err = loadConfig(cfg.Config)
return p, err
}

func (p *plugin) Init() (err error) {
p.i, err = icinga2.New(p.cfg.URL, p.cfg.User, p.cfg.Pass)
if p.cfg.APIUpdateInterval < time.Second {
p.cfg.APIUpdateInterval = time.Second * 30
}
p.hostStatusMap = map[string]int{
"invalid": 0,
"up": 0,
"down": 0,
"unreachable": 0,
}
p.serviceStatusMap = map[string]int{
"invalid": 0,
"ok": 0,
"warning": 0,
"critical": 0,
"unknown": 0,
}

p.servicesDown = []string{}
w := make(chan bool)
go func() {
p.update()
w <- true
for {
time.Sleep(p.cfg.APIUpdateInterval)
p.update()
}
}()
// wait for a sec so we might get status right away instead of starting plugin with no state.
select {
case <-time.After(time.Second):
case <-w:
}
return err
}

func (p *plugin) GetUpdateInterval() int {
return p.cfg.Interval
}
func (p *plugin) UpdatePeriodic() uber.Update {
var update uber.Update
tpl, _ := util.NewTemplate("uberEvent", `{{printf "%+v" .}}`)
// example on how to allow UpdateFromEvent to display for some time
// without being overwritten by periodic updates.
// We set up ts in our plugin, update it in UpdateFromEvent() and just wait if it is in future via helper function
util.WaitForTs(&p.nextTs)
p.Lock()
defer p.Unlock()
update.Markup = `pango`
if p.lastErr != nil {
tpl, _ = util.NewTemplate("uberEvent", `err {{.}}`)
update.FullText = tpl.ExecuteString(p.lastErr)
update.ShortText = fmt.Sprintf("%s", p.lastErr)
update.Color = `#ffcc66`
} else {
tpl, err := util.NewTemplate("uberEvent", `OK:{{ index . "ok"}} W:{{ index . "warning"}} C:{{ index . "critical" }}`)
if err != nil {
panic(err)
}
update.FullText = tpl.ExecuteString(p.serviceStatusMap)
update.ShortText = tpl.ExecuteString(p.serviceStatusMap)
if p.serviceStatusMap["critical"] > 0 {
update.Color = `#ff6666`
} else if p.serviceStatusMap["warning"] > 0 {
update.Color = `#cccc66`
} else if p.serviceStatusMap["unknown"] > 0 {
update.Color = `#6666cc`
} else {
update.Color = `#66cc66`
}
}
return update
}

func (p *plugin) update() {
hosts, err := p.i.GetHostsByFilter(p.cfg.HostFilter)
statusCtr := map[uint8]int{}
for _, h := range hosts {
statusCtr[h.State]++
}
statusMap := map[string]int{
"invalid": statusCtr[uint8(mon.HostInvalid)],
"up": statusCtr[uint8(mon.HostUp)],
"down": statusCtr[uint8(mon.HostDown)],
"unreachable": statusCtr[uint8(mon.HostUnreachable)],
}
p.Lock()
p.hostStatusMap = statusMap
if err != nil {
p.lastErr = err
}
p.Unlock()

services, err2 := p.i.GetServicesByFilter(p.cfg.ServiceFilter)
statusCtr = map[uint8]int{}
servicesNotOk := []string{}
for _, s := range services {
statusCtr[s.State]++
if s.State != uint8(mon.StateOk) {
servicesNotOk = append(servicesNotOk, fmt.Sprintf("%s:%s", s.Host, s.Service))
}

}
statusMap = map[string]int{
"invalid": statusCtr[uint8(mon.StateInvalid)],
"ok": statusCtr[uint8(mon.StateOk)],
"warning": statusCtr[uint8(mon.StateWarning)],
"critical": statusCtr[uint8(mon.StateCritical)],
"unknown": statusCtr[uint8(mon.StateUnknown)],
}
p.Lock()
p.serviceStatusMap = statusMap
p.servicesDown = servicesNotOk
if err != nil {
p.lastErr = err
} else {
p.lastErr = err2
}
p.Unlock()
}

func (p *plugin) UpdateFromEvent(e uber.Event) uber.Update {
var update uber.Update
tpl, _ := util.NewTemplate("uberEvent", `{{printf "%+v" .}}`)
// example on how to allow UpdateFromEvent to display for some time
// without being overwritten by periodic updates.
// We set up ts in our plugin, update it in UpdateFromEvent() and just wait if it is in future via helper function
util.WaitForTs(&p.nextTs)
p.Lock()
defer p.Unlock()
update.Markup = `pango`
if p.lastErr != nil {
tpl, _ = util.NewTemplate("uberEvent", `err {{.}}`)
update.FullText = tpl.ExecuteString(p.lastErr)
update.ShortText = fmt.Sprintf("%s", p.lastErr)
update.Color = `#ffcc66`
} else {
tpl, err := util.NewTemplate("uberEvent", `{{ printf "%+v" . }}`)
if err != nil {
panic(err)
}
update.FullText = tpl.ExecuteString(p.servicesDown)
update.ShortText = tpl.ExecuteString(p.servicesDown)
if p.serviceStatusMap["critical"] > 0 {
update.Color = `#ff6666`
} else if p.serviceStatusMap["warning"] > 0 {
update.Color = `#cccc66`
} else if p.serviceStatusMap["unknown"] > 0 {
update.Color = `#6666cc`
} else {
update.Color = `#66cc66`
}
}

// set next TS updatePeriodic will wait to.
p.nextTs = time.Now().Add(time.Second * 3)
return update
}

// parse received structure into pluginConfig
func loadConfig(c config.PluginConfig) (pluginConfig, error) {
var cfg pluginConfig
cfg.Interval = 10000
cfg.Prefix = "ex: "
// optionally, check for pluginConfig validity after GetConfig call
return cfg, c.GetConfig(&cfg)
}
3 changes: 3 additions & 0 deletions plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"github.com/XANi/uberstatus/config"
"github.com/XANi/uberstatus/plugin/gpu"
"github.com/XANi/uberstatus/plugin/icinga"
"github.com/XANi/uberstatus/plugin/mqtt"
"github.com/XANi/uberstatus/plugin/syncthing"

Expand Down Expand Up @@ -36,6 +37,8 @@ var plugins = map[string]func(uber.PluginConfig) (uber.Plugin, error){
"example": example.New,
"gpu": gpu.New,
"i3blocks": i3blocks.New,
"icinga": icinga.New,
"icinga2": icinga.New,
"memory": memory.New,
"mqtt": mqtt.New,
"network": network.New,
Expand Down

0 comments on commit 9c6f179

Please sign in to comment.