2018-01-29 19:44:18 +00:00
|
|
|
// Copyright (c) 2017 Arista Networks, Inc.
|
|
|
|
// Use of this source code is governed by the Apache License 2.0
|
|
|
|
// that can be found in the COPYING file.
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto/tls"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2018-09-05 15:36:14 +00:00
|
|
|
"github.com/aristanetworks/glog"
|
2018-01-29 19:44:18 +00:00
|
|
|
"github.com/aristanetworks/goarista/gnmi"
|
2018-09-05 15:36:14 +00:00
|
|
|
"github.com/aristanetworks/splunk-hec-go"
|
2018-01-29 19:44:18 +00:00
|
|
|
|
|
|
|
pb "github.com/openconfig/gnmi/proto/gnmi"
|
|
|
|
)
|
|
|
|
|
|
|
|
func exitWithError(s string) {
|
|
|
|
fmt.Fprintln(os.Stderr, s)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
// gNMI options
|
|
|
|
cfg := &gnmi.Config{}
|
|
|
|
flag.StringVar(&cfg.Addr, "addr", "localhost", "gNMI gRPC server `address`")
|
|
|
|
flag.StringVar(&cfg.CAFile, "cafile", "", "Path to server TLS certificate file")
|
|
|
|
flag.StringVar(&cfg.CertFile, "certfile", "", "Path to client TLS certificate file")
|
|
|
|
flag.StringVar(&cfg.KeyFile, "keyfile", "", "Path to client TLS private key file")
|
|
|
|
flag.StringVar(&cfg.Username, "username", "", "Username to authenticate with")
|
|
|
|
flag.StringVar(&cfg.Password, "password", "", "Password to authenticate with")
|
|
|
|
flag.BoolVar(&cfg.TLS, "tls", false, "Enable TLS")
|
|
|
|
subscribePaths := flag.String("paths", "/", "Comma-separated list of paths to subscribe to")
|
|
|
|
|
|
|
|
// Splunk options
|
|
|
|
splunkURLs := flag.String("splunkurls", "https://localhost:8088",
|
|
|
|
"Comma-separated list of URLs of the Splunk servers")
|
|
|
|
splunkToken := flag.String("splunktoken", "", "Token to connect to the Splunk servers")
|
|
|
|
splunkIndex := flag.String("splunkindex", "", "Index for the data in Splunk")
|
|
|
|
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
// gNMI connection
|
|
|
|
ctx := gnmi.NewContext(context.Background(), cfg)
|
|
|
|
// Store the address without the port so it can be used as the host in the Splunk event.
|
|
|
|
addr := cfg.Addr
|
2018-09-05 15:36:14 +00:00
|
|
|
client, err := gnmi.Dial(cfg)
|
|
|
|
if err != nil {
|
|
|
|
glog.Fatal(err)
|
|
|
|
}
|
2018-01-29 19:44:18 +00:00
|
|
|
|
|
|
|
// Splunk connection
|
|
|
|
urls := strings.Split(*splunkURLs, ",")
|
|
|
|
cluster := hec.NewCluster(urls, *splunkToken)
|
|
|
|
cluster.SetHTTPClient(&http.Client{
|
|
|
|
Transport: &http.Transport{
|
|
|
|
// TODO: add flags for TLS
|
|
|
|
TLSClientConfig: &tls.Config{
|
|
|
|
// TODO: add flag to enable TLS
|
|
|
|
InsecureSkipVerify: true,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
// gNMI subscription
|
|
|
|
respChan := make(chan *pb.SubscribeResponse)
|
|
|
|
errChan := make(chan error)
|
|
|
|
defer close(errChan)
|
|
|
|
paths := strings.Split(*subscribePaths, ",")
|
2018-09-05 15:36:14 +00:00
|
|
|
subscribeOptions := &gnmi.SubscribeOptions{
|
|
|
|
Mode: "stream",
|
|
|
|
StreamMode: "target_defined",
|
|
|
|
Paths: gnmi.SplitPaths(paths),
|
|
|
|
}
|
|
|
|
go gnmi.Subscribe(ctx, client, subscribeOptions, respChan, errChan)
|
2018-01-29 19:44:18 +00:00
|
|
|
|
|
|
|
// Forward subscribe responses to Splunk
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
// We got a subscribe response
|
|
|
|
case resp := <-respChan:
|
|
|
|
response := resp.GetResponse()
|
|
|
|
update, ok := response.(*pb.SubscribeResponse_Update)
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert the response into a map[string]interface{}
|
|
|
|
notification, err := gnmi.NotificationToMap(update.Update)
|
|
|
|
if err != nil {
|
|
|
|
exitWithError(err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build the Splunk event
|
|
|
|
path := notification["path"].(string)
|
|
|
|
delete(notification, "path")
|
|
|
|
timestamp := notification["timestamp"].(int64)
|
|
|
|
delete(notification, "timestamp")
|
|
|
|
// Should this be configurable?
|
|
|
|
sourceType := "openconfig"
|
|
|
|
event := &hec.Event{
|
|
|
|
Host: &addr,
|
|
|
|
Index: splunkIndex,
|
|
|
|
Source: &path,
|
|
|
|
SourceType: &sourceType,
|
|
|
|
Event: notification,
|
|
|
|
}
|
|
|
|
event.SetTime(time.Unix(timestamp/1e9, timestamp%1e9))
|
|
|
|
|
|
|
|
// Write the event to Splunk
|
|
|
|
if err := cluster.WriteEvent(event); err != nil {
|
|
|
|
exitWithError("failed to write event: " + err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
// We got an error
|
|
|
|
case err := <-errChan:
|
|
|
|
exitWithError(err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|