You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
263 lines
4.8 KiB
263 lines
4.8 KiB
package main |
|
|
|
import ( |
|
"encoding/csv" |
|
"encoding/json" |
|
"fmt" |
|
"io" |
|
"log" |
|
"os" |
|
"strings" |
|
|
|
"github.com/bytemine/go-icinga2/event" |
|
) |
|
|
|
type icingaConfig struct { |
|
URL string |
|
User string |
|
Password string |
|
Insecure bool |
|
Retries int |
|
} |
|
|
|
type rtConfig struct { |
|
URL string |
|
User string |
|
Password string |
|
Insecure bool |
|
} |
|
|
|
type rt2Config struct { |
|
URL string |
|
User string |
|
Password string |
|
Insecure bool |
|
} |
|
|
|
type cacheConfig struct { |
|
File string |
|
} |
|
|
|
type ticketConfig struct { |
|
Mappings string |
|
mappings []mapping |
|
Nobody string |
|
Queue string |
|
ClosedStatus []string |
|
} |
|
|
|
type config struct { |
|
Icinga icingaConfig |
|
RT rtConfig |
|
RT2 rt2Config |
|
Cache cacheConfig |
|
Ticket ticketConfig |
|
} |
|
|
|
var defaultConfig = config{ |
|
Icinga: icingaConfig{ |
|
URL: "https://monitoring.example.com:5665", |
|
User: "root", |
|
Password: "secret", |
|
Insecure: true, |
|
Retries: 5, |
|
}, |
|
RT: rtConfig{ |
|
URL: "https://support.example.com", |
|
User: "apiuser", |
|
Password: "secret", |
|
Insecure: true, |
|
}, |
|
RT2: rt2Config{ |
|
URL: "https://support.example.com", |
|
User: "apiuser", |
|
Password: "secret", |
|
Insecure: true, |
|
}, |
|
Cache: cacheConfig{ |
|
File: "rotochute..bolt", |
|
}, |
|
Ticket: ticketConfig{ |
|
Mappings: "rotochute.csv", |
|
mappings: []mapping{}, |
|
Nobody: "Nobody", |
|
Queue: "general", |
|
ClosedStatus: []string{ |
|
"done", |
|
"resolved", |
|
"deleted", |
|
}, |
|
}, |
|
} |
|
|
|
func checkConfig(conf *config) error { |
|
if conf.Icinga.URL == "" { |
|
return fmt.Errorf("Icinga.URL must be set.") |
|
} |
|
|
|
if conf.Icinga.User == "" { |
|
return fmt.Errorf("Icinga.User must be set.") |
|
} |
|
|
|
if conf.Icinga.Retries == 0 { |
|
return fmt.Errorf("Icinga.Retries must be > 0.") |
|
} |
|
|
|
if conf.Ticket.Queue == "" { |
|
return fmt.Errorf("Ticket.Queue must be set.") |
|
} |
|
|
|
if conf.Ticket.Nobody == "" { |
|
return fmt.Errorf("Ticket.Nobody must be set.") |
|
} |
|
|
|
if conf.Ticket.Mappings == "" || len(conf.Ticket.Mappings) == 0 { |
|
return fmt.Errorf("Ticket.Mappings must be set.") |
|
} |
|
|
|
if conf.Ticket.ClosedStatus == nil || len(conf.Ticket.ClosedStatus) == 0 { |
|
return fmt.Errorf("Ticket.ClosedStatus must be set.") |
|
} |
|
|
|
if conf.Cache.File == "" { |
|
return fmt.Errorf("Cache.File must be set.") |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func loadConfig(filename string) (*config, error) { |
|
f, err := os.Open(filename) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return readConfig(f) |
|
} |
|
|
|
func readConfig(r io.Reader) (*config, error) { |
|
var c config |
|
|
|
dec := json.NewDecoder(r) |
|
|
|
err := dec.Decode(&c) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
f, err := os.Open(c.Ticket.Mappings) |
|
if err != nil { |
|
log.Fatalf("FATAL: opening mappings file: %s", err) |
|
} |
|
|
|
mappings, err := readMappings(f) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
c.Ticket.mappings = mappings |
|
|
|
return &c, nil |
|
} |
|
|
|
func saveConfig(filename string, c *config) error { |
|
f, err := os.Create(filename) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
return writeConfig(f, c) |
|
} |
|
|
|
func writeConfig(w io.Writer, c *config) error { |
|
x, err := json.MarshalIndent(c, "", "\t") |
|
if err != nil { |
|
return err |
|
} |
|
|
|
_, err = w.Write(x) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func parseCSVBool(value string) (bool, error) { |
|
switch strings.ToLower(value) { |
|
case "true": |
|
return true, nil |
|
case "false": |
|
return false, nil |
|
default: |
|
return false, fmt.Errorf("invalid boolean value: %v", value) |
|
} |
|
} |
|
|
|
const ( |
|
actionStringDelete = "delete" |
|
actionStringComment = "comment" |
|
actionStringCreate = "create" |
|
actionStringIgnore = "ignore" |
|
) |
|
|
|
func parseCSVAction(value string) (actionFunc, error) { |
|
switch strings.ToLower(value) { |
|
case actionStringDelete: |
|
return (*ticketUpdater).delete, nil |
|
case actionStringComment: |
|
return (*ticketUpdater).comment, nil |
|
case actionStringCreate: |
|
return (*ticketUpdater).create, nil |
|
case actionStringIgnore: |
|
return (*ticketUpdater).ignore, nil |
|
default: |
|
return nil, fmt.Errorf("invalid action value: %v", value) |
|
} |
|
|
|
} |
|
|
|
func readMappings(r io.Reader) ([]mapping, error) { |
|
ms := []mapping{} |
|
|
|
x := csv.NewReader(r) |
|
x.Comment = '#' |
|
|
|
// state, old state, existing, owned, action |
|
x.FieldsPerRecord = 4 |
|
line := 0 |
|
for { |
|
line++ |
|
record, err := x.Read() |
|
if err != nil { |
|
if err != io.EOF { |
|
return nil, err |
|
} |
|
|
|
break |
|
} |
|
|
|
// uppercase the value as icingas strings are uppercase |
|
state := event.NewState(strings.ToUpper(record[0])) |
|
if state == event.StateNil { |
|
return nil, fmt.Errorf("error in line %v: invalid state value %v", line, record[0]) |
|
} |
|
|
|
oldState := event.NewState(record[1]) |
|
|
|
owned, err := parseCSVBool(record[2]) |
|
if err != nil { |
|
return nil, fmt.Errorf("error in line %v: %v", line, err) |
|
} |
|
|
|
action, err := parseCSVAction(record[3]) |
|
if err != nil { |
|
return nil, fmt.Errorf("error in line %v: %v", line, err) |
|
} |
|
|
|
m := mapping{condition: condition{state: state, oldState: oldState, owned: owned}, action: action} |
|
ms = append(ms, m) |
|
} |
|
|
|
return ms, nil |
|
}
|
|
|