diff --git a/birdwatcher.go b/birdwatcher.go index c2d1bcc..6b3d75f 100644 --- a/birdwatcher.go +++ b/birdwatcher.go @@ -1,12 +1,115 @@ package main import ( - "github.com/julienschmidt/httprouter" + "flag" + "fmt" + "io/ioutil" "log" + "log/syslog" "net/http" + "os" + "regexp" + + "github.com/julienschmidt/httprouter" + yaml "gopkg.in/yaml.v2" ) +var debug int = 0 +var slog *syslog.Writer // Our syslog connection +var conf Config + +type Match struct { + Expr string // The regular expression as a string. + Fields []string // The named fields for grouped expressions. + Next string // The next regular expression in the flow. +} + +// Compiled regular expression and it's corresponding match data. +type RE struct { + RE *regexp.Regexp + Match Match +} + +// The configuration found in the configuration file. +type FileConfig struct { + Matches map[string]Match // All our regular expressions and related data. + Listen string // Listen to this address:port for HTTP. + FileName string // File to look for patterns + +} + +type Config struct { + Conf FileConfig + Res map[string]RE +} + +// Parse the configuration file. Returns the configuration. +func parseconfig(filename string) (conf *Config, err error) { + conf = new(Config) + + contents, err := ioutil.ReadFile(filename) + if err != nil { + return + } + + fileconf := new(FileConfig) + if err = yaml.Unmarshal(contents, &fileconf); err != nil { + return + } + + conf.Res = make(map[string]RE) + + // Build the regexps from the configuration. + for key, match := range fileconf.Matches { + var err error + var re RE + + re.Match = match + re.RE, err = regexp.Compile(match.Expr) + if err != nil { + slog.Err("Couldn't compile re: " + match.Expr) + os.Exit(-1) + } + + // Check that the number of capturing groups matches the number of expected fields. + lengroups := len(re.RE.SubexpNames()) - 1 + lenfields := len(re.Match.Fields) + + if lengroups != lenfields { + line := fmt.Sprintf("Number of capturing groups (%v) not equal to number of fields (%v): %s", lengroups, lenfields, re.Match.Expr) + slog.Err(line) + os.Exit(-1) + } + + conf.Res[key] = re + } + + return +} + func main() { + var configfile = flag.String("config", "birdwatcher.yaml", "Path to configuration file") + var flagdebug = flag.Int("debug", 0, "Be more verbose") + flag.Parse() + + debug = *flagdebug + + slog, err := syslog.New(syslog.LOG_ERR, "birdwatcher") + if err != nil { + fmt.Printf("Couldn't open syslog") + os.Exit(-1) + } + + slog.Debug("birdwatcher starting") + + conf, err := parseconfig(*configfile) + if err != nil { + slog.Err("Couldn't parse configuration file: " + err.Error()) + os.Exit(-1) + } + + fmt.Printf("%v\n", conf) + r := httprouter.New() r.GET("/status", Status) r.GET("/routes", Routes) diff --git a/birdwatcher.yaml b/birdwatcher.yaml new file mode 100644 index 0000000..c235d5b --- /dev/null +++ b/birdwatcher.yaml @@ -0,0 +1,28 @@ +--- +filename: v4-show-protocols-all.txt + +# A map of all the expected patterns. +# fields: A list of variables to set from the grouped patterns. +# expr: The regular expression. +# next: The next regexp to go to after the current one has a match. "" +# if we are finished. +# action: What to do with the stored values. + +matches: + + # pp_0236_as10310 Pipe master up 2016-07-22 => t_0236_as10310 + getprotocol: + fields: + - name + - prot + - date + - status + expr: "^([A-Z][a-z][0-9]*) (.*) .* (.*) (.*)" + next: "protdescription" + + # Description: Pipe for AS10310 - Yahoo! - VLAN Interface 236 + protdescription: + fields: + - desc + expr: " Description: (.*)" + next: "" diff --git a/patterns.go b/patterns.go new file mode 100644 index 0000000..e4ecce5 --- /dev/null +++ b/patterns.go @@ -0,0 +1,46 @@ +package main + +import ( + "bufio" + "fmt" + "os" +) + +// readLines reads a whole file into memory +// and returns a slice of its lines. +func readLines(path string) ([]string, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + + var lines []string + scanner := bufio.NewScanner(file) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } + return lines, scanner.Err() +} + +// pattern looks for pattern matching regular expression re in the +// lines buffer. Returns the name of our next regular expression to +// look for. +func pattern(re RE, lines []string) (next string) { + for _, line := range lines { + if debug > 1 { + slog.Debug("Looking at: " + line) + } + + fields := re.RE.FindStringSubmatch(line) + + if len(fields)-1 == len(re.Match.Fields) { + if debug > 0 { + line := fmt.Sprintf("Found match for a message of type '%v'", re.Match) + slog.Debug(line) + } + } + } + + return re.Match.Next +} diff --git a/routes.go b/routes.go index 0392dd0..df32824 100644 --- a/routes.go +++ b/routes.go @@ -2,10 +2,18 @@ package main import ( "fmt" - "github.com/julienschmidt/httprouter" "net/http" + + "github.com/julienschmidt/httprouter" ) func Protocols(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { fmt.Fprint(w, "protocols\n") + + lines, err := readLines(conf.Conf.FileName) + if err != nil { + return + } + + pattern(conf.Res["getprotocol"], lines) }