...
 
Commits (11)
......@@ -7,7 +7,13 @@ and this project adheres to [Semantic Versioning](semver.md).
## [Unreleased]
### Added
* Check if the config file exists
* Check for the config in 3 locations; ZCB_CONFIG_PATH, `/etc/zabbix-cachet-bridge.ini` and `./config.ini`
* Read everything from Args into a JSON String
* Exit when Issue already exists
* Exit with code 0 when nothing was to do
### Changed
* Make sure to return exit code 0 at the end
### Deprecated
### Removed
### Fixed
......
......@@ -52,15 +52,16 @@ Here you have to create a Media-Type:
| Name | Cachet |
| Type | Script |
| Script Name | zabbix-cachet-bridge |
| Script Parameters | {ALERT.SUBJECT} |
| |{ALERT.MESSAGE} |
| Script Parameters | {ALERT.MESSAGE} |
In the Message Templates:
| Type | Subject | Message |
|------|---------|----------|
| Problem | Problem: {EVENT.NAME} | {"host":"{HOST.NAME}","status":"started","date":"{EVENT.DATE} {EVENT.TIME}", "problem":"{EVENT.NAME}","severity":"{EVENT.SEVERITY}","event_id":"{EVENT.ID}"} |
| Problem recovery | Resolved in {EVENT.DURATION}: {EVENT.NAME} | {"host":"{HOST.NAME}","status":"resolved","date":"{EVENT.RECOVERY.DATE} {EVENT.RECOVERY.TIME}", "problem":"{EVENT.NAME}","severity":"{EVENT.SEVERITY}","event_id":"{EVENT.ID}"} |
| Problem | Problem: {EVENT.NAME} | {\"host\":\"{HOST.HOST}\",\"status\":\"started\",\"date\":\"{EVENT.DATE} {EVENT.TIME}\", \"problem\":\"{EVENT.NAME}\",\"severity\":\"{EVENT.SEVERITY}\",\"event_id\":{EVENT.ID}} |
| Problem recovery | Resolved in {EVENT.DURATION}: {EVENT.HOST} | {\"host\":\"{HOST.NAME}\",\"status\":\"resolved\",\"date\":\"{EVENT.RECOVERY.DATE} {EVENT.RECOVERY.TIME}\", \"problem\":\"{EVENT.NAME}\",\"severity\":\"{EVENT.SEVERITY}\",\"event_id\":{EVENT.ID}} |
The Subject could be anything, its not used.
#### Configure Media-Type
......@@ -84,7 +85,7 @@ This will do the following things:
* Open and init the Database
* Open and check a connection to Cachet
It will then fail saying its missing some arguments, if you see that notice, everything worked as it should!
It will then fail with `zabbix-cachet-bridge requires a JSON string as input`, if you see that notice, everything worked as it should!
#### Configure Bridge
......
......@@ -5,7 +5,7 @@ go 1.14
require (
github.com/Masterminds/semver v1.5.0
github.com/andygrunwald/cachet v0.0.0-20180404195246-2a1ca51087fd
github.com/go-sql-driver/mysql v1.5.0 // indirect
github.com/go-sql-driver/mysql v1.5.0
github.com/google/go-querystring v1.0.0 // indirect
github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/smartystreets/goconvey v1.6.4 // indirect
......
......@@ -91,7 +91,7 @@ func (d *Database) Schema() {
}
}
func (d *Database) GetOpenIssuesForComponent(cid int) []int {
func (d *Database) GetOpenIssuesForComponentId(cid int) []int {
r, e := d.db.Query(fmt.Sprintf("SELECT incident_id FROM %s WHERE component_id=%d AND is_open=true;", d.databaseTable, cid))
if e != nil {
......@@ -111,7 +111,7 @@ func (d *Database) GetOpenIssuesForComponent(cid int) []int {
return ids
}
func (d *Database) GetIssueForComponent(eid int) int {
func (d *Database) GetIssueForEventId(eid int) int {
r, e := d.db.Query(fmt.Sprintf("SELECT incident_id FROM %s WHERE event_id=%d ORDER BY incident_id DESC LIMIT 1", d.databaseTable, eid))
if e != nil {
......@@ -132,7 +132,28 @@ func (d *Database) GetIssueForComponent(eid int) int {
return id
}
func (d *Database) OpenIssueForComponent(eid int, cid int, iid int) bool {
func (d *Database) IsIssueOpenByEventId(eid int) bool {
r, e := d.db.Query(fmt.Sprintf("SELECT is_open FROM %s WHERE event_id=%d ORDER BY incident_id DESC LIMIT 1", d.databaseTable, eid))
if e != nil {
panic(e)
}
var status bool
if r.Next() {
e := r.Scan(&status)
if e != nil {
panic(e)
}
} else {
return false
}
return status
}
func (d *Database) OpenIssue(eid int, cid int, iid int) bool {
_, e := d.db.Query(fmt.Sprintf("INSERT INTO %s (id, event_id, component_id, incident_id, is_open) VALUES (DEFAULT,%d,%d,%d,true);", d.databaseTable, eid, cid, iid))
if e != nil {
......@@ -142,7 +163,7 @@ func (d *Database) OpenIssueForComponent(eid int, cid int, iid int) bool {
return true
}
func (d *Database) CloseIssueForComponent(eid int) bool {
func (d *Database) CloseIssueForEventId(eid int) bool {
_, e := d.db.Query(fmt.Sprintf("UPDATE %s SET is_open=false WHERE event_id=%d;", d.databaseTable, eid))
if e != nil {
......
......@@ -27,14 +27,9 @@ func main() {
// Stage 1: Load the Config file
fpath := os.Getenv("ZCB_CONFIG_PATH")
var cfg *ini.File
var e error
if len(fpath) >= 1 {
cfg, e = ini.Load(fpath)
} else {
cfg, e = ini.Load("config.ini")
}
path := GetFirstExisting(fpath, "/etc/zabbix-cachet-bridge.ini", "config.ini")
cfg, e := ini.Load(path)
if e != nil {
panic(e)
}
......@@ -46,17 +41,18 @@ func main() {
// Stage 3: Connect to and check the Cachet API
capi := cachet.Init2(cfg)
// Stage 4: Check that we have 2 arguments
args := os.Args
if len(args) != 2 {
println("zabbix-cachet-bridge requires 2 arguments: \"Message Title\" \"{Message JSON}\"")
os.Exit(1)
// Stage 4: Read JSON from STDIN
var zabbixJson string
for i := 1; i < len(os.Args); i++ {
zabbixJson += os.Args[i] + " "
}
// Stage 5: Get and parse arguments
title := args[1]
zabbixJson := args[2]
if len(zabbixJson) <= 0 {
println("zabbix-cachet-bridge requires a escaped JSON string as input")
os.Exit(1)
}
// Stage 5: Parse JSON and get Data
var status zabbix.Status
e = json.Unmarshal([]byte(zabbixJson), &status)
......@@ -64,6 +60,7 @@ func main() {
panic(e)
}
title := status.Problem
hostId, e := cfg.Section("components").Key(status.Host).Int()
if e != nil {
panic(e)
......@@ -85,6 +82,14 @@ func main() {
fixedIssueMessage := cfg.Section("messages").Key("fixed_issue").String()
minimumSeverity := zabbix.GetCachetSeverity(cfg.Section("reporting").Key("minimum_severity").String())
// Check if issue is already active
if statusId == cachet.StatusInvestigating {
if db.IsIssueOpenByEventId(status.EventId) {
println("Issue is already active")
os.Exit(0)
}
}
// Stage 6: Run Incident Create routine if applicable
if statusId == cachet.StatusInvestigating && severity >= minimumSeverity {
notifySubs, e := cfg.Section("reporting").Key("notify_subscribers").Bool()
......@@ -108,16 +113,16 @@ func main() {
panic(e)
}
db.OpenIssueForComponent(status.EventId, hostId, i2.ID)
db.OpenIssue(status.EventId, hostId, i2.ID)
}
// Stage 7: Run Incident Close routine if applicable
if statusId == cachet.StatusFixed {
issue := db.GetIssueForComponent(status.EventId)
issue := db.GetIssueForEventId(status.EventId)
if issue < 0 {
println("No issue to resolve")
return
os.Exit(0)
}
i, _, e := capi.Incidents.Get(issue)
......@@ -144,6 +149,33 @@ func main() {
panic(e)
}
db.CloseIssueForComponent(status.EventId)
db.CloseIssueForEventId(status.EventId)
}
os.Exit(0)
}
func fileExists(filename string) bool {
info, err := os.Stat(filename)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}
func GetFirstExisting(c1 string, c2 string, c3 string) string {
if fileExists(c1) {
return c1
}
if fileExists(c2) {
return c2
}
if fileExists(c3) {
return c3
}
panic("Cannot find any config file!")
return ""
}
\ No newline at end of file