├── .gitignore ├── optics ├── optics.go ├── parser.go └── optics_collector.go ├── environment ├── environment_item.go ├── parser.go └── environment_collector.go ├── bgp ├── bgp_session.go ├── parser.go └── bgp_collector.go ├── util └── util.go ├── facts ├── facts.go ├── parser.go └── facts_collector.go ├── go.mod ├── interfaces ├── interface.go ├── interface_collector.go └── parser.go ├── Dockerfile ├── .goreleaser.yml ├── config.yml.example ├── collector └── rpc_collector.go ├── connector ├── device.go └── connection.go ├── LICENSE ├── rpc └── rpc_client.go ├── devices.go ├── collectors.go ├── README.md ├── cisco_collector.go ├── config └── config.go ├── main.go └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | outputs 2 | id_rsa 3 | cisco_exporter 4 | dist/ 5 | config.yml 6 | -------------------------------------------------------------------------------- /optics/optics.go: -------------------------------------------------------------------------------- 1 | package optics 2 | 3 | type Optics struct { 4 | RxPower float64 5 | TxPower float64 6 | } 7 | -------------------------------------------------------------------------------- /environment/environment_item.go: -------------------------------------------------------------------------------- 1 | package environment 2 | 3 | type EnvironmentItem struct { 4 | Name string 5 | Status string 6 | OK bool 7 | IsTemp bool 8 | Temperature float64 9 | } 10 | -------------------------------------------------------------------------------- /bgp/bgp_session.go: -------------------------------------------------------------------------------- 1 | package bgp 2 | 3 | type BgpSession struct { 4 | IP string 5 | Asn string 6 | Up bool 7 | ReceivedPrefixes float64 8 | InputMessages float64 9 | OutputMessages float64 10 | } 11 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "strconv" 4 | 5 | // Str2float64 converts a string to float64 6 | func Str2float64(str string) float64 { 7 | value, err := strconv.ParseFloat(str, 64) 8 | if err != nil { 9 | return -1 10 | } 11 | return value 12 | } 13 | -------------------------------------------------------------------------------- /facts/facts.go: -------------------------------------------------------------------------------- 1 | package facts 2 | 3 | type VersionFact struct { 4 | Version string 5 | } 6 | 7 | type MemoryFact struct { 8 | Type string 9 | Total float64 10 | Used float64 11 | Free float64 12 | } 13 | 14 | type CPUFact struct { 15 | FiveSeconds float64 16 | Interrupts float64 17 | OneMinute float64 18 | FiveMinutes float64 19 | } 20 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/lwlcom/cisco_exporter 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/pkg/errors v0.9.1 7 | github.com/prometheus/client_golang v1.11.0 8 | github.com/prometheus/common v0.32.1 // indirect 9 | github.com/sirupsen/logrus v1.8.1 10 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 11 | gopkg.in/yaml.v2 v2.4.0 12 | ) 13 | -------------------------------------------------------------------------------- /interfaces/interface.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | type Interface struct { 4 | Name string 5 | MacAddress string 6 | Description string 7 | 8 | AdminStatus string 9 | OperStatus string 10 | 11 | InputErrors float64 12 | OutputErrors float64 13 | 14 | InputDrops float64 15 | OutputDrops float64 16 | 17 | InputBytes float64 18 | OutputBytes float64 19 | 20 | InputBroadcast float64 21 | InputMulticast float64 22 | 23 | Speed string 24 | } 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang as builder 2 | ADD . /go/cisco_exporter/ 3 | WORKDIR /go/cisco_exporter 4 | RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /go/bin/cisco_exporter 5 | 6 | FROM alpine 7 | ENV SSH_KEYFILE "" 8 | ENV CONFIG_FILE "/config.yml" 9 | ENV CMD_FLAGS "" 10 | RUN apk --no-cache add ca-certificates 11 | WORKDIR /app 12 | COPY --from=builder /go/bin/cisco_exporter . 13 | CMD ./cisco_exporter -ssh.keyfile=$SSH_KEYFILE -config.file=$CONFIG_FILE $CMD_FLAGS 14 | EXPOSE 9362 15 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | builds: 2 | - env: 3 | - CGO_ENABLED=0 4 | goos: 5 | - linux 6 | goarch: 7 | - 386 8 | - amd64 9 | - arm 10 | - arm64 11 | archives: 12 | - replacements: 13 | darwin: Darwin 14 | linux: Linux 15 | windows: Windows 16 | 386: i386 17 | amd64: x86_64 18 | checksum: 19 | name_template: 'checksums.txt' 20 | snapshot: 21 | name_template: "{{ .Tag }}-next" 22 | changelog: 23 | sort: asc 24 | filters: 25 | exclude: 26 | - '^docs:' 27 | - '^test:' 28 | release: 29 | github: 30 | -------------------------------------------------------------------------------- /config.yml.example: -------------------------------------------------------------------------------- 1 | --- 2 | debug: false 3 | legacy_ciphers: false 4 | # default values 5 | timeout: 5 6 | batch_size: 10000 7 | username: default-username 8 | password: default-password 9 | key_file: /path/to/key 10 | 11 | devices: 12 | - host: host1.example.com 13 | key_file: /path/to/key 14 | timeout: 5 15 | batch_size: 10000 16 | features: 17 | bgp: false 18 | - host: host2.example.com:2233 19 | username: exporter 20 | password: secret 21 | 22 | features: 23 | bgp: true 24 | environment: true 25 | facts: true 26 | interfaces: true 27 | optics: true 28 | -------------------------------------------------------------------------------- /collector/rpc_collector.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "github.com/lwlcom/cisco_exporter/rpc" 5 | 6 | "github.com/prometheus/client_golang/prometheus" 7 | ) 8 | 9 | // RPCCollector collects metrics from Cisco using rpc.Client 10 | type RPCCollector interface { 11 | // Name returns an human readable name for logging and debugging purposes 12 | Name() string 13 | 14 | // Describe describes the metrics 15 | Describe(ch chan<- *prometheus.Desc) 16 | 17 | // Collect collects metrics from Cisco 18 | Collect(client *rpc.Client, ch chan<- prometheus.Metric, labelValues []string) error 19 | } 20 | -------------------------------------------------------------------------------- /connector/device.go: -------------------------------------------------------------------------------- 1 | package connector 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/lwlcom/cisco_exporter/config" 7 | "golang.org/x/crypto/ssh" 8 | ) 9 | 10 | type Device struct { 11 | Host string 12 | Port string 13 | Auth AuthMethod 14 | ClientConfig ssh.ClientConfig 15 | DeviceConfig *config.DeviceConfig 16 | } 17 | 18 | // AuthMethod is the method to use to authenticate agaist the device 19 | type AuthMethod func(*ssh.ClientConfig) 20 | 21 | // AuthByPassword uses password authentication 22 | func AuthByPassword(username, password string) AuthMethod { 23 | return func(cfg *ssh.ClientConfig) { 24 | cfg.User = username 25 | cfg.Auth = append(cfg.Auth, ssh.Password(password)) 26 | } 27 | } 28 | 29 | // AuthByKey uses public key authentication 30 | func AuthByKey(username string, key io.Reader) (AuthMethod, error) { 31 | pk, err := loadPrivateKey(key) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return func(cfg *ssh.ClientConfig) { 36 | cfg.User = username 37 | cfg.Auth = append(cfg.Auth, pk) 38 | }, nil 39 | } 40 | 41 | func (d *Device) String() string { 42 | return d.Host 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Martin Poppen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bgp/parser.go: -------------------------------------------------------------------------------- 1 | package bgp 2 | 3 | import ( 4 | "errors" 5 | "regexp" 6 | 7 | "github.com/lwlcom/cisco_exporter/rpc" 8 | "github.com/lwlcom/cisco_exporter/util" 9 | ) 10 | 11 | // Parse parses cli output and tries to find bgp sessions with related data 12 | func (c *bgpCollector) Parse(ostype string, output string) ([]BgpSession, error) { 13 | if ostype != rpc.IOSXE && ostype != rpc.NXOS { 14 | return nil, errors.New("'show bgp all summary' is not implemented for " + ostype) 15 | } 16 | items := []BgpSession{} 17 | neighborRegexp, _ := regexp.Compile(`(\S+)\s+\d\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+\s+\d+\s+\d+\s+\S+\s+(\S+)\s*`) 18 | 19 | matches := neighborRegexp.FindAllStringSubmatch(output, -1) 20 | for _, match := range matches { 21 | pref := util.Str2float64(match[5]) 22 | up := true 23 | if pref < 0 { 24 | pref = 0 25 | up = false 26 | } 27 | 28 | item := BgpSession{ 29 | IP: match[1], 30 | Asn: match[2], 31 | InputMessages: util.Str2float64(match[3]), 32 | OutputMessages: util.Str2float64(match[4]), 33 | Up: up, 34 | ReceivedPrefixes: pref, 35 | } 36 | items = append(items, item) 37 | } 38 | return items, nil 39 | } 40 | -------------------------------------------------------------------------------- /rpc/rpc_client.go: -------------------------------------------------------------------------------- 1 | package rpc 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | 8 | "log" 9 | 10 | "github.com/lwlcom/cisco_exporter/connector" 11 | ) 12 | 13 | const ( 14 | IOSXE string = "IOSXE" 15 | NXOS string = "NXOS" 16 | IOS string = "IOS" 17 | ) 18 | 19 | // Client sends commands to a Cisco device 20 | type Client struct { 21 | conn *connector.SSHConnection 22 | Debug bool 23 | OSType string 24 | } 25 | 26 | // NewClient creates a new client connection 27 | func NewClient(ssh *connector.SSHConnection, debug bool) *Client { 28 | rpc := &Client{conn: ssh, Debug: debug} 29 | 30 | return rpc 31 | } 32 | 33 | // Identify tries to identify the OS running on a Cisco device 34 | func (c *Client) Identify() error { 35 | output, err := c.RunCommand("show version") 36 | if err != nil { 37 | return err 38 | } 39 | switch { 40 | case strings.Contains(output, "IOS XE"): 41 | c.OSType = IOSXE 42 | case strings.Contains(output, "NX-OS"): 43 | c.OSType = NXOS 44 | case strings.Contains(output, "IOS Software"): 45 | c.OSType = IOS 46 | default: 47 | return errors.New("Unknown OS") 48 | } 49 | if c.Debug { 50 | log.Printf("Host %s identified as: %s\n", c.conn.Host, c.OSType) 51 | } 52 | return nil 53 | } 54 | 55 | // RunCommand runs a command on a Cisco device 56 | func (c *Client) RunCommand(cmd string) (string, error) { 57 | if c.Debug { 58 | log.Printf("Running command on %s: %s\n", c.conn.Host, cmd) 59 | } 60 | output, err := c.conn.RunCommand(fmt.Sprintf("%s", cmd)) 61 | if err != nil { 62 | println(err.Error()) 63 | return "", err 64 | } 65 | 66 | return output, nil 67 | } 68 | -------------------------------------------------------------------------------- /optics/parser.go: -------------------------------------------------------------------------------- 1 | package optics 2 | 3 | import ( 4 | "errors" 5 | "regexp" 6 | "strings" 7 | 8 | "github.com/lwlcom/cisco_exporter/rpc" 9 | "github.com/lwlcom/cisco_exporter/util" 10 | ) 11 | 12 | // ParseInterfaces parses cli output and returns list of interface names 13 | func (c *opticsCollector) ParseInterfaces(ostype string, output string) ([]string, error) { 14 | if ostype != rpc.IOSXE && ostype != rpc.NXOS && ostype != rpc.IOS { 15 | return nil, errors.New("'show interfaces stats' is not implemented for " + ostype) 16 | } 17 | var items []string 18 | deviceNameRegexp, _ := regexp.Compile(`^([a-zA-Z0-9\/\.-]+)\s*`) 19 | lines := strings.Split(output, "\n") 20 | for _, line := range lines { 21 | matches := deviceNameRegexp.FindStringSubmatch(line) 22 | if matches == nil { 23 | continue 24 | } 25 | items = append(items, matches[1]) 26 | } 27 | return items, nil 28 | } 29 | 30 | // ParseTransceiver parses cli output and tries to find tx/rx power for an interface 31 | func (c *opticsCollector) ParseTransceiver(ostype string, output string) (Optics, error) { 32 | if ostype != rpc.IOSXE && ostype != rpc.NXOS && ostype != rpc.IOS { 33 | return Optics{}, errors.New("Transceiver data is not implemented for " + ostype) 34 | } 35 | transceiverRegexp := make(map[string]*regexp.Regexp) 36 | transceiverRegexp[rpc.IOS], _ = regexp.Compile(`\S+\s+(?:(?:-)?\d+\.\d+)\s+(?:(?:-)?\d+\.\d+)\s+((?:-)?\d+\.\d+)\s+((?:-)?\d+\.\d+)\s*`) 37 | transceiverRegexp[rpc.NXOS], _ = regexp.Compile(`\s*Tx Power\s*((?:-)?\d+\.\d+).*\s*Rx Power\s*((?:-)?\d+\.\d+).*`) 38 | transceiverRegexp[rpc.IOSXE], _ = regexp.Compile(`\s+Transceiver Tx power\s+= ((?:-)?\d+\.\d+).*\s*Transceiver Rx optical power\s+= ((?:-)?\d+\.\d+).*`) 39 | 40 | matches := transceiverRegexp[ostype].FindStringSubmatch(output) 41 | if matches == nil { 42 | return Optics{}, errors.New("Transceiver not found") 43 | } 44 | return Optics{ 45 | TxPower: util.Str2float64(matches[1]), 46 | RxPower: util.Str2float64(matches[2]), 47 | }, nil 48 | } 49 | -------------------------------------------------------------------------------- /environment/parser.go: -------------------------------------------------------------------------------- 1 | package environment 2 | 3 | import ( 4 | "errors" 5 | "regexp" 6 | "strings" 7 | 8 | "github.com/lwlcom/cisco_exporter/rpc" 9 | "github.com/lwlcom/cisco_exporter/util" 10 | ) 11 | 12 | // Parse parses cli output and tries to find oll temperature and power related data 13 | func (c *environmentCollector) Parse(ostype string, output string) ([]EnvironmentItem, error) { 14 | if ostype != rpc.IOSXE && ostype != rpc.NXOS && ostype != rpc.IOS { 15 | return nil, errors.New("'show environment' is not implemented for " + ostype) 16 | } 17 | items := []EnvironmentItem{} 18 | tempRegexp := make(map[string]*regexp.Regexp) 19 | powerRegexp := make(map[string]*regexp.Regexp) 20 | tempRegexp[rpc.IOSXE], _ = regexp.Compile(`\s*(\w\w)\s*Temp: (\w+)\s+\w+\s+(\d+) Celsius`) 21 | powerRegexp[rpc.IOSXE], _ = regexp.Compile(`\s*(\w\w)\s*PEM (\w+)\s+(\w+)\s+\d*\s[\s\w]*`) 22 | tempRegexp[rpc.IOS], _ = regexp.Compile(`^(\d+)\s+(air \w+(?: +\w+)?)\s+(\d+)C \(.*\)\s+\w+$`) 23 | powerRegexp[rpc.IOS], _ = regexp.Compile(`^(\w+)\s+.+\s+(AC) \w+\s+(\w+)\s+\w+\s+.+\s+.+$`) 24 | tempRegexp[rpc.NXOS], _ = regexp.Compile(`^(\d+)\s+(.+)\s+\d\d?\s+\d\d?\s+(\d\d?)\s+\w+\s*$`) 25 | powerRegexp[rpc.NXOS], _ = regexp.Compile(`^(\d+)\s+.+\s+(AC)\s+.+\s+.+\s+(\w+)\s*$`) 26 | 27 | lines := strings.Split(output, "\n") 28 | for _, line := range lines { 29 | if matches := tempRegexp[ostype].FindStringSubmatch(line); matches != nil { 30 | x := EnvironmentItem{ 31 | Name: strings.TrimSpace(matches[1] + " " + matches[2]), 32 | IsTemp: true, 33 | Temperature: util.Str2float64(matches[3]), 34 | } 35 | items = append(items, x) 36 | } else if matches := powerRegexp[ostype].FindStringSubmatch(line); matches != nil { 37 | ok := matches[3] == "Normal" || matches[3] == "good" || matches[3] == "ok" 38 | x := EnvironmentItem{ 39 | Name: strings.TrimSpace(matches[1] + " " + matches[2]), 40 | IsTemp: false, 41 | OK: ok, 42 | Status: matches[3], 43 | } 44 | items = append(items, x) 45 | } 46 | } 47 | return items, nil 48 | } 49 | -------------------------------------------------------------------------------- /environment/environment_collector.go: -------------------------------------------------------------------------------- 1 | package environment 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/lwlcom/cisco_exporter/rpc" 7 | 8 | "github.com/lwlcom/cisco_exporter/collector" 9 | "github.com/prometheus/client_golang/prometheus" 10 | ) 11 | 12 | const prefix string = "cisco_environment_" 13 | 14 | var ( 15 | temperaturesDesc *prometheus.Desc 16 | powerSupplyDesc *prometheus.Desc 17 | ) 18 | 19 | func init() { 20 | l := []string{"target", "item"} 21 | temperaturesDesc = prometheus.NewDesc(prefix+"sensor_temp", "Sensor temperatures", l, nil) 22 | l = append(l, "status") 23 | powerSupplyDesc = prometheus.NewDesc(prefix+"power_up", "Status of power supplies (1 OK, 0 Something is wrong)", l, nil) 24 | } 25 | 26 | type environmentCollector struct { 27 | } 28 | 29 | // NewCollector creates a new collector 30 | func NewCollector() collector.RPCCollector { 31 | return &environmentCollector{} 32 | } 33 | 34 | // Name returns the name of the collector 35 | func (*environmentCollector) Name() string { 36 | return "Environment" 37 | } 38 | 39 | // Describe describes the metrics 40 | func (*environmentCollector) Describe(ch chan<- *prometheus.Desc) { 41 | ch <- temperaturesDesc 42 | ch <- powerSupplyDesc 43 | } 44 | 45 | // Collect collects metrics from Cisco 46 | func (c *environmentCollector) Collect(client *rpc.Client, ch chan<- prometheus.Metric, labelValues []string) error { 47 | out, err := client.RunCommand("show environment") 48 | if err != nil { 49 | return err 50 | } 51 | items, err := c.Parse(client.OSType, out) 52 | if err != nil { 53 | if client.Debug { 54 | log.Printf("Parse environment for %s: %s\n", labelValues[0], err.Error()) 55 | } 56 | return nil 57 | } 58 | 59 | for _, item := range items { 60 | l := append(labelValues, item.Name) 61 | if item.IsTemp { 62 | ch <- prometheus.MustNewConstMetric(temperaturesDesc, prometheus.GaugeValue, float64(item.Temperature), l...) 63 | } else { 64 | val := 0 65 | if item.OK { 66 | val = 1 67 | } 68 | l = append(l, item.Status) 69 | ch <- prometheus.MustNewConstMetric(powerSupplyDesc, prometheus.GaugeValue, float64(val), l...) 70 | } 71 | } 72 | 73 | return nil 74 | } 75 | -------------------------------------------------------------------------------- /devices.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | 7 | "github.com/lwlcom/cisco_exporter/config" 8 | "github.com/lwlcom/cisco_exporter/connector" 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | func devicesForConfig(cfg *config.Config) ([]*connector.Device, error) { 13 | devs := make([]*connector.Device, len(cfg.Devices)) 14 | var err error 15 | for i, d := range cfg.Devices { 16 | devs[i], err = deviceFromDeviceConfig(d, cfg) 17 | if err != nil { 18 | return nil, err 19 | } 20 | } 21 | 22 | return devs, nil 23 | } 24 | 25 | func deviceFromDeviceConfig(device *config.DeviceConfig, cfg *config.Config) (*connector.Device, error) { 26 | auth, err := authForDevice(device, cfg) 27 | if err != nil { 28 | return nil, errors.Wrapf(err, "could not initialize config for device %s", device.Host) 29 | } 30 | 31 | port := "22" 32 | host := device.Host 33 | if strings.Contains(host, ":") { 34 | d := strings.Split(host, ":") 35 | host = d[0] 36 | port = d[1] 37 | } 38 | 39 | return &connector.Device{ 40 | Host: host, 41 | Port: port, 42 | Auth: auth, 43 | DeviceConfig: device, 44 | }, nil 45 | } 46 | 47 | func authForDevice(device *config.DeviceConfig, cfg *config.Config) (connector.AuthMethod, error) { 48 | user := cfg.Username 49 | if device.Username != nil { 50 | user = *device.Username 51 | } 52 | 53 | if device.KeyFile != nil { 54 | return authForKeyFile(user, *device.KeyFile) 55 | } 56 | 57 | if cfg.KeyFile != "" { 58 | return authForKeyFile(user, cfg.KeyFile) 59 | } 60 | 61 | if device.Password != nil { 62 | return connector.AuthByPassword(user, *device.Password), nil 63 | } 64 | 65 | if cfg.Password != "" { 66 | return connector.AuthByPassword(user, cfg.Password), nil 67 | } 68 | 69 | return nil, errors.New("no valid authentication method available") 70 | } 71 | 72 | func authForKeyFile(username, keyFile string) (connector.AuthMethod, error) { 73 | f, err := os.Open(keyFile) 74 | if err != nil { 75 | return nil, errors.Wrap(err, "could not open ssh key file") 76 | } 77 | defer f.Close() 78 | auth, err := connector.AuthByKey(username, f) 79 | if err != nil { 80 | return nil, errors.Wrap(err, "could not load ssh private key file") 81 | } 82 | 83 | return auth, nil 84 | } 85 | -------------------------------------------------------------------------------- /collectors.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/lwlcom/cisco_exporter/bgp" 5 | "github.com/lwlcom/cisco_exporter/collector" 6 | "github.com/lwlcom/cisco_exporter/config" 7 | "github.com/lwlcom/cisco_exporter/connector" 8 | "github.com/lwlcom/cisco_exporter/environment" 9 | "github.com/lwlcom/cisco_exporter/facts" 10 | "github.com/lwlcom/cisco_exporter/interfaces" 11 | "github.com/lwlcom/cisco_exporter/optics" 12 | ) 13 | 14 | type collectors struct { 15 | collectors map[string]collector.RPCCollector 16 | devices map[string][]collector.RPCCollector 17 | cfg *config.Config 18 | } 19 | 20 | func collectorsForDevices(devices []*connector.Device, cfg *config.Config) *collectors { 21 | c := &collectors{ 22 | collectors: make(map[string]collector.RPCCollector), 23 | devices: make(map[string][]collector.RPCCollector), 24 | cfg: cfg, 25 | } 26 | 27 | for _, d := range devices { 28 | c.initCollectorsForDevice(d) 29 | } 30 | 31 | return c 32 | } 33 | 34 | func (c *collectors) initCollectorsForDevice(device *connector.Device) { 35 | f := c.cfg.FeaturesForDevice(device.Host) 36 | 37 | c.devices[device.Host] = make([]collector.RPCCollector, 0) 38 | c.addCollectorIfEnabledForDevice(device, "bgp", f.BGP, bgp.NewCollector) 39 | c.addCollectorIfEnabledForDevice(device, "environment", f.Environment, environment.NewCollector) 40 | c.addCollectorIfEnabledForDevice(device, "facts", f.Facts, facts.NewCollector) 41 | c.addCollectorIfEnabledForDevice(device, "interfaces", f.Interfaces, interfaces.NewCollector) 42 | c.addCollectorIfEnabledForDevice(device, "optics", f.Optics, optics.NewCollector) 43 | 44 | } 45 | 46 | func (c *collectors) addCollectorIfEnabledForDevice(device *connector.Device, key string, enabled *bool, newCollector func() collector.RPCCollector) { 47 | if !*enabled { 48 | return 49 | } 50 | 51 | col, found := c.collectors[key] 52 | if !found { 53 | col = newCollector() 54 | c.collectors[key] = col 55 | } 56 | 57 | c.devices[device.Host] = append(c.devices[device.Host], col) 58 | } 59 | 60 | func (c *collectors) allEnabledCollectors() []collector.RPCCollector { 61 | collectors := make([]collector.RPCCollector, len(c.collectors)) 62 | 63 | i := 0 64 | for _, collector := range c.collectors { 65 | collectors[i] = collector 66 | i++ 67 | } 68 | 69 | return collectors 70 | } 71 | 72 | func (c *collectors) collectorsForDevice(device *connector.Device) []collector.RPCCollector { 73 | cols, found := c.devices[device.Host] 74 | if !found { 75 | return []collector.RPCCollector{} 76 | } 77 | 78 | return cols 79 | } 80 | -------------------------------------------------------------------------------- /bgp/bgp_collector.go: -------------------------------------------------------------------------------- 1 | package bgp 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/lwlcom/cisco_exporter/rpc" 7 | 8 | "github.com/lwlcom/cisco_exporter/collector" 9 | "github.com/prometheus/client_golang/prometheus" 10 | ) 11 | 12 | const prefix string = "cisco_bgp_session_" 13 | 14 | var ( 15 | upDesc *prometheus.Desc 16 | receivedPrefixesDesc *prometheus.Desc 17 | inputMessagesDesc *prometheus.Desc 18 | outputMessagesDesc *prometheus.Desc 19 | ) 20 | 21 | func init() { 22 | l := []string{"target", "asn", "ip"} 23 | upDesc = prometheus.NewDesc(prefix+"up", "Session is up (1 = Established)", l, nil) 24 | receivedPrefixesDesc = prometheus.NewDesc(prefix+"prefixes_received_count", "Number of received prefixes", l, nil) 25 | inputMessagesDesc = prometheus.NewDesc(prefix+"messages_input_count", "Number of received messages", l, nil) 26 | outputMessagesDesc = prometheus.NewDesc(prefix+"messages_output_count", "Number of transmitted messages", l, nil) 27 | } 28 | 29 | type bgpCollector struct { 30 | } 31 | 32 | // NewCollector creates a new collector 33 | func NewCollector() collector.RPCCollector { 34 | return &bgpCollector{} 35 | } 36 | 37 | // Name returns the name of the collector 38 | func (*bgpCollector) Name() string { 39 | return "BGP" 40 | } 41 | 42 | // Describe describes the metrics 43 | func (*bgpCollector) Describe(ch chan<- *prometheus.Desc) { 44 | ch <- upDesc 45 | ch <- receivedPrefixesDesc 46 | ch <- inputMessagesDesc 47 | ch <- outputMessagesDesc 48 | } 49 | 50 | // Collect collects metrics from Cisco 51 | func (c *bgpCollector) Collect(client *rpc.Client, ch chan<- prometheus.Metric, labelValues []string) error { 52 | out, err := client.RunCommand("show bgp all summary") 53 | if err != nil { 54 | return err 55 | } 56 | items, err := c.Parse(client.OSType, out) 57 | if err != nil { 58 | if client.Debug { 59 | log.Printf("Parse bgp sessions for %s: %s\n", labelValues[0], err.Error()) 60 | } 61 | return nil 62 | } 63 | 64 | for _, item := range items { 65 | l := append(labelValues, item.Asn, item.IP) 66 | 67 | up := 0 68 | if item.Up { 69 | up = 1 70 | } 71 | 72 | ch <- prometheus.MustNewConstMetric(upDesc, prometheus.GaugeValue, float64(up), l...) 73 | ch <- prometheus.MustNewConstMetric(receivedPrefixesDesc, prometheus.GaugeValue, float64(item.ReceivedPrefixes), l...) 74 | ch <- prometheus.MustNewConstMetric(inputMessagesDesc, prometheus.GaugeValue, float64(item.InputMessages), l...) 75 | ch <- prometheus.MustNewConstMetric(outputMessagesDesc, prometheus.GaugeValue, float64(item.OutputMessages), l...) 76 | } 77 | 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /facts/parser.go: -------------------------------------------------------------------------------- 1 | package facts 2 | 3 | import ( 4 | "errors" 5 | "regexp" 6 | "strings" 7 | 8 | "github.com/lwlcom/cisco_exporter/rpc" 9 | "github.com/lwlcom/cisco_exporter/util" 10 | ) 11 | 12 | // ParseVersion parses cli output and tries to find the version number of the running OS 13 | func (c *factsCollector) ParseVersion(ostype string, output string) (VersionFact, error) { 14 | if ostype != rpc.IOSXE && ostype != rpc.NXOS && ostype != rpc.IOS { 15 | return VersionFact{}, errors.New("'show version' is not implemented for " + ostype) 16 | } 17 | versionRegexp := make(map[string]*regexp.Regexp) 18 | versionRegexp[rpc.IOSXE], _ = regexp.Compile(`^.*, Version (.+) -.*$`) 19 | versionRegexp[rpc.IOS], _ = regexp.Compile(`^.*, Version (.+),.*$`) 20 | versionRegexp[rpc.NXOS], _ = regexp.Compile(`^\s+NXOS: version (.*)$`) 21 | 22 | lines := strings.Split(output, "\n") 23 | for _, line := range lines { 24 | matches := versionRegexp[ostype].FindStringSubmatch(line) 25 | if matches == nil { 26 | continue 27 | } 28 | return VersionFact{Version: ostype + "-" + matches[1]}, nil 29 | } 30 | return VersionFact{}, errors.New("Version string not found") 31 | } 32 | 33 | // ParseMemory parses cli output and tries to find current memory usage 34 | func (c *factsCollector) ParseMemory(ostype string, output string) ([]MemoryFact, error) { 35 | if ostype != rpc.IOSXE && ostype != rpc.IOS { 36 | return nil, errors.New("'show process memory' is not implemented for " + ostype) 37 | } 38 | memoryRegexp, _ := regexp.Compile(`^\s*(\S*) Pool Total:\s*(\d+) Used:\s*(\d+) Free:\s*(\d+)\s*$`) 39 | 40 | items := []MemoryFact{} 41 | lines := strings.Split(output, "\n") 42 | for _, line := range lines { 43 | matches := memoryRegexp.FindStringSubmatch(line) 44 | if matches == nil { 45 | continue 46 | } 47 | item := MemoryFact{ 48 | Type: matches[1], 49 | Total: util.Str2float64(matches[2]), 50 | Used: util.Str2float64(matches[3]), 51 | Free: util.Str2float64(matches[4]), 52 | } 53 | items = append(items, item) 54 | } 55 | return items, nil 56 | } 57 | 58 | // ParseCPU parses cli output and tries to find current CPU utilization 59 | func (c *factsCollector) ParseCPU(ostype string, output string) (CPUFact, error) { 60 | if ostype != rpc.IOSXE && ostype != rpc.IOS { 61 | return CPUFact{}, errors.New("'show process cpu' is not implemented for " + ostype) 62 | } 63 | memoryRegexp, _ := regexp.Compile(`^\s*CPU utilization for five seconds: (\d+)%\/(\d+)%; one minute: (\d+)%; five minutes: (\d+)%.*$`) 64 | 65 | lines := strings.Split(output, "\n") 66 | for _, line := range lines { 67 | matches := memoryRegexp.FindStringSubmatch(line) 68 | if matches == nil { 69 | continue 70 | } 71 | return CPUFact{ 72 | FiveSeconds: util.Str2float64(matches[1]), 73 | Interrupts: util.Str2float64(matches[2]), 74 | OneMinute: util.Str2float64(matches[3]), 75 | FiveMinutes: util.Str2float64(matches[4]), 76 | }, nil 77 | } 78 | return CPUFact{}, errors.New("Version string not found") 79 | } 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cisco_exporter 2 | Exporter for metrics from devices running Cisco (NX-OS/IOS XE/IOS) (via SSH) https://prometheus.io/ 3 | 4 | The basic structure is based on https://github.com/czerwonk/junos_exporter 5 | 6 | # flags 7 | Name | Description | Default 8 | ---------|-------------|--------- 9 | version | Print version information. | 10 | web.listen-address | Address on which to expose metrics and web interface. | :9362 11 | web.telemetry-path | Path under which to expose metrics. | /metrics 12 | ssh.targets | Comma seperated list of hosts to scrape | 13 | ssh.user | Username to use for SSH connection | cisco_exporter 14 | ssh.keyfile | Key file to use for SSH connection | cisco_exporter 15 | ssh.timeout | Timeout in seconds to use for SSH connection | 5 16 | debug | Show verbose debug output | false 17 | legacy.ciphers | Allow insecure legacy ciphers: aes128-cbc 3des-cbc aes192-cbc aes256-cbc | false 18 | config.file | Path to config file | 19 | 20 | # metrics 21 | 22 | All metrics are enabled by default. To disable something pass a flag `--.enabled=false`, where `` is the name of the metric. 23 | 24 | Name | Description | OS 25 | ---------|-------------|---- 26 | bgp | BGP (message count, prefix counts per peer, session state) | IOS XE/NX-OS 27 | environment | Environment (temperatures, state of power supply) | NX-OS/IOS XE/IOS 28 | facts | System informations (OS Version, memory: total/used/free, cpu: 5s/1m/5m/interrupts) | IOS XE/IOS 29 | interfaces | Interfaces (transmitted/received: bytes/errors/drops, admin/oper state) | NX-OS (*_drops is always 0)/IOS XE/IOS 30 | optics | Optical signals (tx/rx) | NX-OS/IOS XE/IOS 31 | 32 | ## Install 33 | ```bash 34 | go get -u github.com/lwlcom/cisco_exporter 35 | ``` 36 | 37 | ## Usage 38 | 39 | ### Binary 40 | ```bash 41 | ./cisco_exporter -ssh.targets="host1.example.com,host2.example.com:2233,172.16.0.1" -ssh.keyfile=cisco_exporter 42 | ``` 43 | 44 | ```bash 45 | ./cisco_exporter -config.file=config.yml 46 | ``` 47 | 48 | ## Config file 49 | The exporter can be configured with a YAML based config file: 50 | 51 | ```yaml 52 | --- 53 | debug: false 54 | legacy_ciphers: false 55 | # default values 56 | timeout: 5 57 | batch_size: 10000 58 | username: default-username 59 | password: default-password 60 | key_file: /path/to/key 61 | 62 | devices: 63 | - host: host1.example.com 64 | key_file: /path/to/key 65 | timeout: 5 66 | batch_size: 10000 67 | features: # enable/disable per host 68 | bgp: false 69 | - host: host2.example.com:2233 70 | username: exporter 71 | password: secret 72 | 73 | features: 74 | bgp: true 75 | environment: true 76 | facts: true 77 | interfaces: true 78 | optics: true 79 | 80 | ``` 81 | 82 | ## Third Party Components 83 | This software uses components of the following projects 84 | * Prometheus Go client library (https://github.com/prometheus/client_golang) 85 | 86 | ## License 87 | (c) Martin Poppen, 2018. Licensed under [MIT](LICENSE) license. 88 | 89 | ## Prometheus 90 | see https://prometheus.io/ 91 | -------------------------------------------------------------------------------- /cisco_collector.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "sync" 7 | 8 | "github.com/lwlcom/cisco_exporter/connector" 9 | "github.com/lwlcom/cisco_exporter/rpc" 10 | "github.com/prometheus/client_golang/prometheus" 11 | log "github.com/sirupsen/logrus" 12 | ) 13 | 14 | const prefix = "cisco_" 15 | 16 | var ( 17 | scrapeCollectorDurationDesc *prometheus.Desc 18 | scrapeDurationDesc *prometheus.Desc 19 | upDesc *prometheus.Desc 20 | ) 21 | 22 | func init() { 23 | upDesc = prometheus.NewDesc(prefix+"up", "Scrape of target was successful", []string{"target"}, nil) 24 | scrapeDurationDesc = prometheus.NewDesc(prefix+"collector_duration_seconds", "Duration of a collector scrape for one target", []string{"target"}, nil) 25 | scrapeCollectorDurationDesc = prometheus.NewDesc(prefix+"collect_duration_seconds", "Duration of a scrape by collector and target", []string{"target", "collector"}, nil) 26 | } 27 | 28 | type ciscoCollector struct { 29 | devices []*connector.Device 30 | collectors *collectors 31 | } 32 | 33 | func newCiscoCollector(devices []*connector.Device) *ciscoCollector { 34 | return &ciscoCollector{ 35 | devices: devices, 36 | collectors: collectorsForDevices(devices, cfg), 37 | } 38 | } 39 | 40 | // Describe implements prometheus.Collector interface 41 | func (c *ciscoCollector) Describe(ch chan<- *prometheus.Desc) { 42 | ch <- upDesc 43 | ch <- scrapeDurationDesc 44 | ch <- scrapeCollectorDurationDesc 45 | 46 | for _, col := range c.collectors.allEnabledCollectors() { 47 | col.Describe(ch) 48 | } 49 | } 50 | 51 | // Collect implements prometheus.Collector interface 52 | func (c *ciscoCollector) Collect(ch chan<- prometheus.Metric) { 53 | wg := &sync.WaitGroup{} 54 | 55 | wg.Add(len(c.devices)) 56 | for _, d := range c.devices { 57 | go c.collectForHost(d, ch, wg) 58 | } 59 | 60 | wg.Wait() 61 | } 62 | 63 | func (c *ciscoCollector) collectForHost(device *connector.Device, ch chan<- prometheus.Metric, wg *sync.WaitGroup) { 64 | defer wg.Done() 65 | 66 | l := []string{device.Host} 67 | 68 | t := time.Now() 69 | defer func() { 70 | ch <- prometheus.MustNewConstMetric(scrapeDurationDesc, prometheus.GaugeValue, time.Since(t).Seconds(), l...) 71 | }() 72 | 73 | conn, err := connector.NewSSSHConnection(device, cfg) 74 | if err != nil { 75 | log.Errorln(err) 76 | ch <- prometheus.MustNewConstMetric(upDesc, prometheus.GaugeValue, 0, l...) 77 | return 78 | } 79 | defer conn.Close() 80 | 81 | ch <- prometheus.MustNewConstMetric(upDesc, prometheus.GaugeValue, 1, l...) 82 | 83 | client := rpc.NewClient(conn, cfg.Debug) 84 | err = client.Identify() 85 | if err != nil { 86 | log.Errorln(device.Host + ": " + err.Error()) 87 | return 88 | } 89 | 90 | for _, col := range c.collectors.collectorsForDevice(device) { 91 | ct := time.Now() 92 | err := col.Collect(client, ch, l) 93 | 94 | if err != nil && err.Error() != "EOF" { 95 | log.Errorln(col.Name() + ": " + err.Error()) 96 | } 97 | 98 | ch <- prometheus.MustNewConstMetric(scrapeCollectorDurationDesc, prometheus.GaugeValue, time.Since(ct).Seconds(), append(l, col.Name())...) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /optics/optics_collector.go: -------------------------------------------------------------------------------- 1 | package optics 2 | 3 | import ( 4 | "log" 5 | "regexp" 6 | 7 | "github.com/lwlcom/cisco_exporter/rpc" 8 | 9 | "github.com/lwlcom/cisco_exporter/collector" 10 | "github.com/prometheus/client_golang/prometheus" 11 | ) 12 | 13 | const prefix string = "cisco_optics_" 14 | 15 | var ( 16 | opticsTXDesc *prometheus.Desc 17 | opticsRXDesc *prometheus.Desc 18 | ) 19 | 20 | func init() { 21 | l := []string{"target", "interface"} 22 | opticsTXDesc = prometheus.NewDesc(prefix+"tx", "Transceiver Tx power", l, nil) 23 | opticsRXDesc = prometheus.NewDesc(prefix+"rx", "Transceiver Rx power", l, nil) 24 | } 25 | 26 | type opticsCollector struct { 27 | } 28 | 29 | // NewCollector creates a new collector 30 | func NewCollector() collector.RPCCollector { 31 | return &opticsCollector{} 32 | } 33 | 34 | // Name returns the name of the collector 35 | func (*opticsCollector) Name() string { 36 | return "Optics" 37 | } 38 | 39 | // Describe describes the metrics 40 | func (*opticsCollector) Describe(ch chan<- *prometheus.Desc) { 41 | ch <- opticsTXDesc 42 | ch <- opticsRXDesc 43 | } 44 | 45 | // Collect collects metrics from Cisco 46 | func (c *opticsCollector) Collect(client *rpc.Client, ch chan<- prometheus.Metric, labelValues []string) error { 47 | var iflistcmd string 48 | 49 | switch client.OSType { 50 | case rpc.IOS, rpc.IOSXE: 51 | iflistcmd = "show interfaces stats | exclude disabled" 52 | case rpc.NXOS: 53 | iflistcmd = "show interface status | exclude disabled | exclude notconn | exclude sfpAbsent | exclude --------------------------------------------------------------------------------" 54 | } 55 | out, err := client.RunCommand(iflistcmd) 56 | 57 | if err != nil { 58 | return err 59 | } 60 | interfaces, err := c.ParseInterfaces(client.OSType, out) 61 | if err != nil { 62 | if client.Debug { 63 | log.Printf("ParseInterfaces for %s: %s\n", labelValues[0], err.Error()) 64 | } 65 | return nil 66 | } 67 | 68 | xeDev, _ := regexp.Compile(`\S(\d+)/(\d+)/(\d+)`) 69 | 70 | for _, i := range interfaces { 71 | switch client.OSType { 72 | case rpc.IOS: 73 | out, err = client.RunCommand("show interfaces " + i + " transceiver") 74 | case rpc.NXOS: 75 | out, err = client.RunCommand("show interface " + i + " transceiver details") 76 | case rpc.IOSXE: 77 | matches := xeDev.FindStringSubmatch(i) 78 | if matches == nil { 79 | continue 80 | } 81 | out, err = client.RunCommand("show hw-module subslot " + matches[1] + "/" + matches[2] + " transceiver " + matches[3] + " status") 82 | } 83 | if err != nil { 84 | if client.Debug { 85 | log.Printf("Transceiver command on %s: %s\n", labelValues[0], err.Error()) 86 | } 87 | continue 88 | } 89 | optic, err := c.ParseTransceiver(client.OSType, out) 90 | if err != nil { 91 | if client.Debug { 92 | log.Printf("Transceiver data for %s: %s\n", labelValues[0], err.Error()) 93 | } 94 | continue 95 | } 96 | l := append(labelValues, i) 97 | 98 | ch <- prometheus.MustNewConstMetric(opticsTXDesc, prometheus.GaugeValue, float64(optic.TxPower), l...) 99 | ch <- prometheus.MustNewConstMetric(opticsRXDesc, prometheus.GaugeValue, float64(optic.RxPower), l...) 100 | } 101 | 102 | return nil 103 | } 104 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "io" 5 | "io/ioutil" 6 | "strings" 7 | 8 | "gopkg.in/yaml.v2" 9 | ) 10 | 11 | // Config represents the configuration for the exporter 12 | type Config struct { 13 | Debug bool `yaml:"debug"` 14 | LegacyCiphers bool `yaml:"legacy_ciphers,omitempty"` 15 | Timeout int `yaml:"timeout,omitempty"` 16 | BatchSize int `yaml:"batch_size,omitempty"` 17 | Username string `yaml:"username,omitempty"` 18 | Password string `yaml:"Password,omitempty"` 19 | KeyFile string `yaml:"key_file,omitempty"` 20 | Devices []*DeviceConfig `yaml:"devices,omitempty"` 21 | Features *FeatureConfig `yaml:"features,omitempty"` 22 | } 23 | 24 | // DeviceConfig is the config representation of 1 device 25 | type DeviceConfig struct { 26 | Host string `yaml:"host"` 27 | Username *string `yaml:"username,omitempty"` 28 | Password *string `yaml:"password,omitempty"` 29 | KeyFile *string `yaml:"key_file,omitempty"` 30 | LegacyCiphers *bool `yaml:"legacy_ciphers,omitempty"` 31 | Timeout *int `yaml:"timeout,omitempty"` 32 | BatchSize *int `yaml:"batch_size,omitempty"` 33 | Features *FeatureConfig `yaml:"features,omitempty"` 34 | } 35 | 36 | // FeatureConfig is the list of collectors enabled or disabled 37 | type FeatureConfig struct { 38 | BGP *bool `yaml:"bgp,omitempty"` 39 | Environment *bool `yaml:"environment,omitempty"` 40 | Facts *bool `yaml:"facts,omitempty"` 41 | Interfaces *bool `yaml:"interfaces,omitempty"` 42 | Optics *bool `yaml:"optics,omitempty"` 43 | } 44 | 45 | // New creates a new config 46 | func New() *Config { 47 | c := &Config{ 48 | Features: &FeatureConfig{}, 49 | } 50 | c.setDefaultValues() 51 | 52 | return c 53 | } 54 | 55 | // Load loads a config from reader 56 | func Load(reader io.Reader) (*Config, error) { 57 | b, err := ioutil.ReadAll(reader) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | c := New() 63 | err = yaml.Unmarshal(b, c) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | for _, d := range c.Devices { 69 | if d.Features == nil { 70 | continue 71 | } 72 | if d.Features.BGP == nil { 73 | d.Features.BGP = c.Features.BGP 74 | } 75 | if d.Features.Environment == nil { 76 | d.Features.Environment = c.Features.Environment 77 | } 78 | if d.Features.Facts == nil { 79 | d.Features.Facts = c.Features.Facts 80 | } 81 | if d.Features.Interfaces == nil { 82 | d.Features.Interfaces = c.Features.Interfaces 83 | } 84 | if d.Features.Optics == nil { 85 | d.Features.Optics = c.Features.Optics 86 | } 87 | } 88 | 89 | return c, nil 90 | } 91 | 92 | func (c *Config) setDefaultValues() { 93 | c.Debug = false 94 | c.LegacyCiphers = false 95 | c.Timeout = 5 96 | c.BatchSize = 10000 97 | 98 | f := c.Features 99 | bgp := true 100 | f.BGP = &bgp 101 | environment := true 102 | f.Environment = &environment 103 | facts := true 104 | f.Facts = &facts 105 | interfaces := true 106 | f.Interfaces = &interfaces 107 | optics := true 108 | f.Optics = &optics 109 | } 110 | 111 | // DevicesFromTargets creates devices configs from targets list 112 | func (c *Config) DevicesFromTargets(sshHosts string) { 113 | targets := strings.Split(sshHosts, ",") 114 | 115 | c.Devices = make([]*DeviceConfig, len(targets)) 116 | for i, target := range targets { 117 | c.Devices[i] = &DeviceConfig{ 118 | Host: target, 119 | } 120 | } 121 | } 122 | 123 | // FeaturesForDevice gets the feature set configured for a device 124 | func (c *Config) FeaturesForDevice(host string) *FeatureConfig { 125 | d := c.findDeviceConfig(host) 126 | 127 | if d != nil && d.Features != nil { 128 | return d.Features 129 | } 130 | 131 | return c.Features 132 | } 133 | 134 | func (c *Config) findDeviceConfig(host string) *DeviceConfig { 135 | for _, dc := range c.Devices { 136 | if dc.Host == host { 137 | return dc 138 | } 139 | } 140 | 141 | return nil 142 | } 143 | -------------------------------------------------------------------------------- /connector/connection.go: -------------------------------------------------------------------------------- 1 | package connector 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "io/ioutil" 7 | "regexp" 8 | "strings" 9 | 10 | "time" 11 | 12 | "github.com/lwlcom/cisco_exporter/config" 13 | "github.com/pkg/errors" 14 | "golang.org/x/crypto/ssh" 15 | ) 16 | 17 | // NewSSSHConnection connects to device 18 | func NewSSSHConnection(device *Device, cfg *config.Config) (*SSHConnection, error) { 19 | deviceConfig := device.DeviceConfig 20 | 21 | legacyCiphers := cfg.LegacyCiphers 22 | if deviceConfig.LegacyCiphers != nil { 23 | legacyCiphers = *deviceConfig.LegacyCiphers 24 | } 25 | 26 | batchSize := cfg.BatchSize 27 | if deviceConfig.BatchSize != nil { 28 | batchSize = *deviceConfig.BatchSize 29 | } 30 | 31 | timeout := cfg.Timeout 32 | if deviceConfig.Timeout != nil { 33 | timeout = *deviceConfig.Timeout 34 | } 35 | 36 | sshConfig := &ssh.ClientConfig{ 37 | HostKeyCallback: ssh.InsecureIgnoreHostKey(), 38 | Timeout: time.Duration(timeout) * time.Second, 39 | } 40 | if legacyCiphers { 41 | sshConfig.SetDefaults() 42 | sshConfig.Ciphers = append(sshConfig.Ciphers, "aes128-cbc", "3des-cbc") 43 | } 44 | 45 | device.Auth(sshConfig) 46 | 47 | c := &SSHConnection{ 48 | Host: device.Host + ":" + device.Port, 49 | batchSize: batchSize, 50 | clientConfig: sshConfig, 51 | } 52 | 53 | err := c.Connect() 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | return c, nil 59 | } 60 | 61 | // SSHConnection encapsulates the connection to the device 62 | type SSHConnection struct { 63 | client *ssh.Client 64 | Host string 65 | stdin io.WriteCloser 66 | stdout io.Reader 67 | session *ssh.Session 68 | batchSize int 69 | clientConfig *ssh.ClientConfig 70 | } 71 | 72 | // Connect connects to the device 73 | func (c *SSHConnection) Connect() error { 74 | var err error 75 | c.client, err = ssh.Dial("tcp", c.Host, c.clientConfig) 76 | if err != nil { 77 | return err 78 | } 79 | 80 | session, err := c.client.NewSession() 81 | if err != nil { 82 | c.client.Conn.Close() 83 | return err 84 | } 85 | c.stdin, _ = session.StdinPipe() 86 | c.stdout, _ = session.StdoutPipe() 87 | modes := ssh.TerminalModes{ 88 | ssh.ECHO: 0, 89 | ssh.OCRNL: 0, 90 | } 91 | session.RequestPty("vt100", 0, 2000, modes) 92 | session.Shell() 93 | c.session = session 94 | 95 | c.RunCommand("") 96 | c.RunCommand("terminal length 0") 97 | 98 | return nil 99 | } 100 | 101 | type result struct { 102 | output string 103 | err error 104 | } 105 | 106 | // RunCommand runs a command against the device 107 | func (c *SSHConnection) RunCommand(cmd string) (string, error) { 108 | buf := bufio.NewReader(c.stdout) 109 | io.WriteString(c.stdin, cmd+"\n") 110 | 111 | outputChan := make(chan result) 112 | go func() { 113 | c.readln(outputChan, cmd, buf) 114 | }() 115 | select { 116 | case res := <-outputChan: 117 | return res.output, res.err 118 | case <-time.After(c.clientConfig.Timeout): 119 | return "", errors.New("Timeout reached") 120 | } 121 | } 122 | 123 | // Close closes connection 124 | func (c *SSHConnection) Close() { 125 | if c.client.Conn == nil { 126 | return 127 | } 128 | c.client.Conn.Close() 129 | if c.session != nil { 130 | c.session.Close() 131 | } 132 | } 133 | 134 | func loadPrivateKey(r io.Reader) (ssh.AuthMethod, error) { 135 | b, err := ioutil.ReadAll(r) 136 | if err != nil { 137 | return nil, errors.Wrap(err, "could not read from reader") 138 | } 139 | 140 | key, err := ssh.ParsePrivateKey(b) 141 | if err != nil { 142 | return nil, errors.Wrap(err, "could not parse private key") 143 | } 144 | 145 | return ssh.PublicKeys(key), nil 146 | } 147 | 148 | func (c *SSHConnection) readln(ch chan result, cmd string, r io.Reader) { 149 | re := regexp.MustCompile(`.+#\s?$`) 150 | buf := make([]byte, c.batchSize) 151 | loadStr := "" 152 | for { 153 | n, err := r.Read(buf) 154 | if err != nil { 155 | ch <- result{output: "", err: err} 156 | } 157 | loadStr += string(buf[:n]) 158 | if strings.Contains(loadStr, cmd) && re.MatchString(loadStr) { 159 | break 160 | } 161 | } 162 | loadStr = strings.Replace(loadStr, "\r", "", -1) 163 | ch <- result{output: loadStr, err: nil} 164 | } 165 | -------------------------------------------------------------------------------- /facts/facts_collector.go: -------------------------------------------------------------------------------- 1 | package facts 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/lwlcom/cisco_exporter/rpc" 7 | 8 | "github.com/lwlcom/cisco_exporter/collector" 9 | "github.com/prometheus/client_golang/prometheus" 10 | ) 11 | 12 | const prefix string = "cisco_facts_" 13 | 14 | var ( 15 | versionDesc *prometheus.Desc 16 | memoryTotalDesc *prometheus.Desc 17 | memoryUsedDesc *prometheus.Desc 18 | memoryFreeDesc *prometheus.Desc 19 | cpuOneMinuteDesc *prometheus.Desc 20 | cpuFiveSecondsDesc *prometheus.Desc 21 | cpuInterruptsDesc *prometheus.Desc 22 | cpuFiveMinutesDesc *prometheus.Desc 23 | ) 24 | 25 | func init() { 26 | l := []string{"target"} 27 | versionDesc = prometheus.NewDesc(prefix+"version", "Running OS version", append(l, "version"), nil) 28 | 29 | memoryTotalDesc = prometheus.NewDesc(prefix+"memory_total", "Total memory", append(l, "type"), nil) 30 | memoryUsedDesc = prometheus.NewDesc(prefix+"memory_used", "Used memory", append(l, "type"), nil) 31 | memoryFreeDesc = prometheus.NewDesc(prefix+"memory_free", "Free memory", append(l, "type"), nil) 32 | 33 | cpuOneMinuteDesc = prometheus.NewDesc(prefix+"cpu_one_minute_percent", "CPU utilization for one minute", l, nil) 34 | cpuFiveSecondsDesc = prometheus.NewDesc(prefix+"cpu_five_seconds_percent", "CPU utilization for five seconds", l, nil) 35 | cpuInterruptsDesc = prometheus.NewDesc(prefix+"cpu_interrupt_percent", "Interrupt percentage", l, nil) 36 | cpuFiveMinutesDesc = prometheus.NewDesc(prefix+"cpu_five_minutes_percent", "CPU utilization for five minutes", l, nil) 37 | } 38 | 39 | type factsCollector struct { 40 | } 41 | 42 | // NewCollector creates a new collector 43 | func NewCollector() collector.RPCCollector { 44 | return &factsCollector{} 45 | } 46 | 47 | // Name returns the name of the collector 48 | func (*factsCollector) Name() string { 49 | return "Facts" 50 | } 51 | 52 | // Describe describes the metrics 53 | func (*factsCollector) Describe(ch chan<- *prometheus.Desc) { 54 | ch <- versionDesc 55 | ch <- memoryTotalDesc 56 | ch <- memoryUsedDesc 57 | ch <- memoryFreeDesc 58 | } 59 | 60 | // CollectVersion collects version informations from Cisco 61 | func (c *factsCollector) CollectVersion(client *rpc.Client, ch chan<- prometheus.Metric, labelValues []string) error { 62 | out, err := client.RunCommand("show version") 63 | if err != nil { 64 | return err 65 | } 66 | item, err := c.ParseVersion(client.OSType, out) 67 | if err != nil { 68 | return err 69 | } 70 | l := append(labelValues, item.Version) 71 | ch <- prometheus.MustNewConstMetric(versionDesc, prometheus.GaugeValue, 1, l...) 72 | return nil 73 | } 74 | 75 | // CollectMemory collects memory informations from Cisco 76 | func (c *factsCollector) CollectMemory(client *rpc.Client, ch chan<- prometheus.Metric, labelValues []string) error { 77 | out, err := client.RunCommand("show process memory") 78 | if err != nil { 79 | return err 80 | } 81 | items, err := c.ParseMemory(client.OSType, out) 82 | if err != nil { 83 | return err 84 | } 85 | for _, item := range items { 86 | l := append(labelValues, item.Type) 87 | ch <- prometheus.MustNewConstMetric(memoryTotalDesc, prometheus.GaugeValue, item.Total, l...) 88 | ch <- prometheus.MustNewConstMetric(memoryUsedDesc, prometheus.GaugeValue, item.Used, l...) 89 | ch <- prometheus.MustNewConstMetric(memoryFreeDesc, prometheus.GaugeValue, item.Free, l...) 90 | } 91 | return nil 92 | } 93 | 94 | // CollectCPU collects cpu informations from Cisco 95 | func (c *factsCollector) CollectCPU(client *rpc.Client, ch chan<- prometheus.Metric, labelValues []string) error { 96 | out, err := client.RunCommand("show process cpu") 97 | if err != nil { 98 | return err 99 | } 100 | item, err := c.ParseCPU(client.OSType, out) 101 | if err != nil { 102 | return err 103 | } 104 | ch <- prometheus.MustNewConstMetric(cpuOneMinuteDesc, prometheus.GaugeValue, item.OneMinute, labelValues...) 105 | ch <- prometheus.MustNewConstMetric(cpuFiveSecondsDesc, prometheus.GaugeValue, item.FiveSeconds, labelValues...) 106 | ch <- prometheus.MustNewConstMetric(cpuInterruptsDesc, prometheus.GaugeValue, item.Interrupts, labelValues...) 107 | ch <- prometheus.MustNewConstMetric(cpuFiveMinutesDesc, prometheus.GaugeValue, item.FiveMinutes, labelValues...) 108 | return nil 109 | } 110 | 111 | // Collect collects metrics from Cisco 112 | func (c *factsCollector) Collect(client *rpc.Client, ch chan<- prometheus.Metric, labelValues []string) error { 113 | err := c.CollectVersion(client, ch, labelValues) 114 | if client.Debug && err != nil { 115 | log.Printf("CollectVersion for %s: %s\n", labelValues[0], err.Error()) 116 | } 117 | err = c.CollectMemory(client, ch, labelValues) 118 | if client.Debug && err != nil { 119 | log.Printf("CollectMemory for %s: %s\n", labelValues[0], err.Error()) 120 | } 121 | err = c.CollectCPU(client, ch, labelValues) 122 | if client.Debug && err != nil { 123 | log.Printf("CollectCPU for %s: %s\n", labelValues[0], err.Error()) 124 | } 125 | return nil 126 | } 127 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "os" 10 | 11 | "github.com/lwlcom/cisco_exporter/config" 12 | "github.com/lwlcom/cisco_exporter/connector" 13 | "github.com/prometheus/client_golang/prometheus" 14 | "github.com/prometheus/client_golang/prometheus/promhttp" 15 | log "github.com/sirupsen/logrus" 16 | ) 17 | 18 | const version string = "0.2" 19 | 20 | var ( 21 | showVersion = flag.Bool("version", false, "Print version information.") 22 | listenAddress = flag.String("web.listen-address", ":9362", "Address on which to expose metrics and web interface.") 23 | metricsPath = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.") 24 | sshHosts = flag.String("ssh.targets", "", "SSH Hosts to scrape") 25 | sshUsername = flag.String("ssh.user", "cisco_exporter", "Username to use for SSH connection") 26 | sshPassword = flag.String("ssh.password", "", "Password to use for SSH connection") 27 | sshKeyFile = flag.String("ssh.keyfile", "", "Key file to use for SSH connection") 28 | sshTimeout = flag.Int("ssh.timeout", 5, "Timeout to use for SSH connection") 29 | sshBatchSize = flag.Int("ssh.batch-size", 10000, "The SSH response batch size") 30 | debug = flag.Bool("debug", false, "Show verbose debug output in log") 31 | legacyCiphers = flag.Bool("legacy.ciphers", false, "Allow legacy CBC ciphers") 32 | bgpEnabled = flag.Bool("bgp.enabled", true, "Scrape bgp metrics") 33 | environmentEnabled = flag.Bool("environment.enabled", true, "Scrape environment metrics") 34 | factsEnabled = flag.Bool("facts.enabled", true, "Scrape system metrics") 35 | interfacesEnabled = flag.Bool("interfaces.enabled", true, "Scrape interface metrics") 36 | opticsEnabled = flag.Bool("optics.enabled", true, "Scrape optic metrics") 37 | configFile = flag.String("config.file", "", "Path to config file") 38 | devices []*connector.Device 39 | cfg *config.Config 40 | ) 41 | 42 | func init() { 43 | flag.Usage = func() { 44 | fmt.Println("Usage: cisco_exporter [ ... ]\n\nParameters:") 45 | fmt.Println() 46 | flag.PrintDefaults() 47 | } 48 | } 49 | 50 | func main() { 51 | flag.Parse() 52 | 53 | if *showVersion { 54 | printVersion() 55 | os.Exit(0) 56 | } 57 | 58 | err := initialize() 59 | if err != nil { 60 | log.Fatalf("could not initialize exporter. %v", err) 61 | } 62 | 63 | startServer() 64 | } 65 | 66 | func loadConfig() (*config.Config, error) { 67 | if len(*configFile) == 0 { 68 | log.Infoln("Loading config flags") 69 | return loadConfigFromFlags(), nil 70 | } 71 | 72 | log.Infoln("Loading config from", *configFile) 73 | b, err := ioutil.ReadFile(*configFile) 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | return config.Load(bytes.NewReader(b)) 79 | } 80 | 81 | func initialize() error { 82 | c, err := loadConfig() 83 | if err != nil { 84 | return err 85 | } 86 | 87 | devices, err = devicesForConfig(c) 88 | if err != nil { 89 | return err 90 | } 91 | cfg = c 92 | 93 | return nil 94 | } 95 | 96 | func loadConfigFromFlags() *config.Config { 97 | c := config.New() 98 | 99 | c.Debug = *debug 100 | c.LegacyCiphers = *legacyCiphers 101 | c.Timeout = *sshTimeout 102 | c.BatchSize = *sshBatchSize 103 | c.Username = *sshUsername 104 | c.Password = *sshPassword 105 | 106 | c.KeyFile = *sshKeyFile 107 | 108 | c.DevicesFromTargets(*sshHosts) 109 | 110 | f := c.Features 111 | f.BGP = bgpEnabled 112 | f.Environment = environmentEnabled 113 | f.Facts = factsEnabled 114 | f.Interfaces = interfacesEnabled 115 | f.Optics = opticsEnabled 116 | 117 | return c 118 | } 119 | 120 | func printVersion() { 121 | fmt.Println("cisco_exporter") 122 | fmt.Printf("Version: %s\n", version) 123 | fmt.Println("Author(s): Martin Poppen") 124 | fmt.Println("Metric exporter for switches and routers running cisco IOS/NX-OS/IOS-XE") 125 | } 126 | 127 | func startServer() { 128 | log.Infof("Starting Cisco exporter (Version: %s)\n", version) 129 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 130 | w.Write([]byte(` 131 | Cisco Exporter (Version ` + version + `) 132 | 133 |

Cisco Exporter

134 |

Metrics

135 |

More information:

136 |

github.com/lwlcom/cisco_exporter

137 | 138 | `)) 139 | }) 140 | http.HandleFunc(*metricsPath, handleMetricsRequest) 141 | 142 | log.Infof("Listening for %s on %s\n", *metricsPath, *listenAddress) 143 | log.Fatal(http.ListenAndServe(*listenAddress, nil)) 144 | } 145 | 146 | func handleMetricsRequest(w http.ResponseWriter, r *http.Request) { 147 | reg := prometheus.NewRegistry() 148 | 149 | c := newCiscoCollector(devices) 150 | reg.MustRegister(c) 151 | 152 | l := log.New() 153 | l.Level = log.ErrorLevel 154 | 155 | promhttp.HandlerFor(reg, promhttp.HandlerOpts{ 156 | ErrorLog: l, 157 | ErrorHandling: promhttp.ContinueOnError}).ServeHTTP(w, r) 158 | } 159 | -------------------------------------------------------------------------------- /interfaces/interface_collector.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/lwlcom/cisco_exporter/rpc" 7 | 8 | "github.com/lwlcom/cisco_exporter/collector" 9 | "github.com/prometheus/client_golang/prometheus" 10 | ) 11 | 12 | const prefix string = "cisco_interface_" 13 | 14 | var ( 15 | receiveBytesDesc *prometheus.Desc 16 | receiveErrorsDesc *prometheus.Desc 17 | receiveDropsDesc *prometheus.Desc 18 | receiveBroadcastDesc *prometheus.Desc 19 | receiveMulticastDesc *prometheus.Desc 20 | transmitBytesDesc *prometheus.Desc 21 | transmitErrorsDesc *prometheus.Desc 22 | transmitDropsDesc *prometheus.Desc 23 | adminStatusDesc *prometheus.Desc 24 | operStatusDesc *prometheus.Desc 25 | errorStatusDesc *prometheus.Desc 26 | ) 27 | 28 | func init() { 29 | l := []string{"target", "name", "description", "mac", "speed"} 30 | receiveBytesDesc = prometheus.NewDesc(prefix+"receive_bytes", "Received data in bytes", l, nil) 31 | receiveErrorsDesc = prometheus.NewDesc(prefix+"receive_errors", "Number of errors caused by incoming packets", l, nil) 32 | receiveDropsDesc = prometheus.NewDesc(prefix+"receive_drops", "Number of dropped incoming packets", l, nil) 33 | receiveBroadcastDesc = prometheus.NewDesc(prefix+"receive_broadcast", "Received broadcast packets", l, nil) 34 | receiveMulticastDesc = prometheus.NewDesc(prefix+"receive_multicast", "Received multicast packets", l, nil) 35 | transmitBytesDesc = prometheus.NewDesc(prefix+"transmit_bytes", "Transmitted data in bytes", l, nil) 36 | transmitErrorsDesc = prometheus.NewDesc(prefix+"transmit_errors", "Number of errors caused by outgoing packets", l, nil) 37 | transmitDropsDesc = prometheus.NewDesc(prefix+"transmit_drops", "Number of dropped outgoing packets", l, nil) 38 | adminStatusDesc = prometheus.NewDesc(prefix+"admin_up", "Admin operational status", l, nil) 39 | operStatusDesc = prometheus.NewDesc(prefix+"up", "Interface operational status", l, nil) 40 | errorStatusDesc = prometheus.NewDesc(prefix+"error_status", "Admin and operational status differ", l, nil) 41 | } 42 | 43 | type interfaceCollector struct { 44 | } 45 | 46 | // NewCollector creates a new collector 47 | func NewCollector() collector.RPCCollector { 48 | return &interfaceCollector{} 49 | } 50 | 51 | // Name returns the name of the collector 52 | func (*interfaceCollector) Name() string { 53 | return "Interfaces" 54 | } 55 | 56 | // Describe describes the metrics 57 | func (*interfaceCollector) Describe(ch chan<- *prometheus.Desc) { 58 | ch <- receiveBytesDesc 59 | ch <- receiveErrorsDesc 60 | ch <- receiveDropsDesc 61 | ch <- receiveBroadcastDesc 62 | ch <- receiveMulticastDesc 63 | ch <- transmitBytesDesc 64 | ch <- transmitDropsDesc 65 | ch <- transmitErrorsDesc 66 | ch <- adminStatusDesc 67 | ch <- operStatusDesc 68 | ch <- errorStatusDesc 69 | } 70 | 71 | // Collect collects metrics from Cisco 72 | func (c *interfaceCollector) Collect(client *rpc.Client, ch chan<- prometheus.Metric, labelValues []string) error { 73 | out, err := client.RunCommand("show interface") 74 | if err != nil { 75 | return err 76 | } 77 | items, err := c.Parse(client.OSType, out) 78 | if err != nil { 79 | if client.Debug { 80 | log.Printf("Parse interfaces for %s: %s\n", labelValues[0], err.Error()) 81 | } 82 | return nil 83 | } 84 | if client.OSType == rpc.IOSXE { 85 | out, err := client.RunCommand("show vlans") 86 | if err != nil { 87 | return err 88 | } 89 | vlans, err := c.ParseVlans(client.OSType, out) 90 | if err != nil { 91 | if client.Debug { 92 | log.Printf("Parse vlans for %s: %s\n", labelValues[0], err.Error()) 93 | } 94 | return nil 95 | } 96 | for _, vlan := range vlans { 97 | for i, item := range items { 98 | if item.Name == vlan.Name { 99 | items[i].InputBytes = vlan.InputBytes 100 | items[i].OutputBytes = vlan.OutputBytes 101 | break 102 | } 103 | } 104 | } 105 | } 106 | 107 | for _, item := range items { 108 | l := append(labelValues, item.Name, item.Description, item.MacAddress, item.Speed) 109 | 110 | errorStatus := 0 111 | if item.AdminStatus != item.OperStatus { 112 | errorStatus = 1 113 | } 114 | adminStatus := 0 115 | if item.AdminStatus == "up" { 116 | adminStatus = 1 117 | } 118 | operStatus := 0 119 | if item.OperStatus == "up" { 120 | operStatus = 1 121 | } 122 | ch <- prometheus.MustNewConstMetric(receiveBytesDesc, prometheus.GaugeValue, item.InputBytes, l...) 123 | ch <- prometheus.MustNewConstMetric(receiveErrorsDesc, prometheus.GaugeValue, item.InputErrors, l...) 124 | ch <- prometheus.MustNewConstMetric(receiveDropsDesc, prometheus.GaugeValue, item.InputDrops, l...) 125 | ch <- prometheus.MustNewConstMetric(transmitBytesDesc, prometheus.GaugeValue, item.OutputBytes, l...) 126 | ch <- prometheus.MustNewConstMetric(transmitErrorsDesc, prometheus.GaugeValue, item.OutputErrors, l...) 127 | ch <- prometheus.MustNewConstMetric(transmitDropsDesc, prometheus.GaugeValue, item.OutputDrops, l...) 128 | ch <- prometheus.MustNewConstMetric(receiveBroadcastDesc, prometheus.GaugeValue, item.InputBroadcast, l...) 129 | ch <- prometheus.MustNewConstMetric(receiveMulticastDesc, prometheus.GaugeValue, item.InputMulticast, l...) 130 | ch <- prometheus.MustNewConstMetric(adminStatusDesc, prometheus.GaugeValue, float64(adminStatus), l...) 131 | ch <- prometheus.MustNewConstMetric(operStatusDesc, prometheus.GaugeValue, float64(operStatus), l...) 132 | ch <- prometheus.MustNewConstMetric(errorStatusDesc, prometheus.GaugeValue, float64(errorStatus), l...) 133 | } 134 | 135 | return nil 136 | } 137 | -------------------------------------------------------------------------------- /interfaces/parser.go: -------------------------------------------------------------------------------- 1 | package interfaces 2 | 3 | import ( 4 | "errors" 5 | "regexp" 6 | "strings" 7 | 8 | "github.com/lwlcom/cisco_exporter/rpc" 9 | "github.com/lwlcom/cisco_exporter/util" 10 | ) 11 | 12 | // Parse parses cli output and tries to find interfaces with related stats 13 | func (c *interfaceCollector) Parse(ostype string, output string) ([]Interface, error) { 14 | if ostype != rpc.IOSXE && ostype != rpc.NXOS && ostype != rpc.IOS { 15 | return nil, errors.New("'show interface' is not implemented for " + ostype) 16 | } 17 | items := []Interface{} 18 | txNXOS := regexp.MustCompile(`^\s+TX$`) // NX OS 19 | newIfRegexp := regexp.MustCompile(`(?:^!?(?: |admin|show|.+#).*$|^$)`) 20 | macRegexp := regexp.MustCompile(`^\s+Hardware(?: is|:) .+, address(?: is|:) (.*) \(.*\)$`) 21 | deviceNameRegexp := regexp.MustCompile(`^([a-zA-Z0-9\/\.-]+) is.*$`) 22 | adminStatusRegexp := regexp.MustCompile(`^.+ is (administratively)?\s*(up|down).*, line protocol is.*$`) 23 | adminStatusNXOSRegexp := regexp.MustCompile(`^\S+ is (up|down)(?:\s|,)?(\(Administratively down\))?.*$`) 24 | descRegexp := regexp.MustCompile(`^\s+Description: (.*)$`) 25 | dropsRegexp := regexp.MustCompile(`^\s+Input queue: \d+\/\d+\/(\d+)\/\d+ .+ Total output drops: (\d+)$`) 26 | multiBroadNXOS := regexp.MustCompile(`^.* (\d+) multicast packets\s+(\d+) broadcast packets$`) // NX OS 27 | multiBroadIOSXE := regexp.MustCompile(`^\s+Received\s+(\d+)\sbroadcasts \((\d+) (?:IP\s)?multicast(?:s)?\)`) // IOS XE 28 | multiBroadIOS := regexp.MustCompile(`^\s*Received (\d+) broadcasts.*$`) // IOS 29 | inputBytesRegexp := regexp.MustCompile(`^\s+\d+ (?:packets input,|input packets)\s+(\d+) bytes.*$`) 30 | outputBytesRegexp := regexp.MustCompile(`^\s+\d+ (?:packets output,|output packets)\s+(\d+) bytes.*$`) 31 | inputErrorsRegexp := regexp.MustCompile(`^\s+(\d+) input error(?:s,)? .*$`) 32 | outputErrorsRegexp := regexp.MustCompile(`^\s+(\d+) output error(?:s,)? .*$`) 33 | speedRegexp := regexp.MustCompile(`^\s+(.*)-duplex,\s(\d+) ((\wb)/s).*$`) 34 | 35 | isRx := true 36 | current := Interface{} 37 | lines := strings.Split(output, "\n") 38 | for _, line := range lines { 39 | if !newIfRegexp.MatchString(line) { 40 | if current != (Interface{}) { 41 | items = append(items, current) 42 | } 43 | matches := deviceNameRegexp.FindStringSubmatch(line) 44 | if matches == nil { 45 | continue 46 | } 47 | current = Interface{ 48 | Name: matches[1], 49 | } 50 | isRx = true 51 | } 52 | if current == (Interface{}) { 53 | continue 54 | } 55 | 56 | if matches := adminStatusRegexp.FindStringSubmatch(line); matches != nil { 57 | if matches[1] == "" { 58 | current.AdminStatus = "up" 59 | } else { 60 | current.AdminStatus = "down" 61 | } 62 | current.OperStatus = matches[2] 63 | } else if matches := adminStatusNXOSRegexp.FindStringSubmatch(line); matches != nil { 64 | if matches[2] == "" { 65 | current.AdminStatus = "up" 66 | } else { 67 | current.AdminStatus = "down" 68 | } 69 | current.OperStatus = matches[1] 70 | } else if matches := descRegexp.FindStringSubmatch(line); matches != nil { 71 | current.Description = matches[1] 72 | } else if matches := macRegexp.FindStringSubmatch(line); matches != nil { 73 | current.MacAddress = matches[1] 74 | } else if matches := dropsRegexp.FindStringSubmatch(line); matches != nil { 75 | current.InputDrops = util.Str2float64(matches[1]) 76 | current.OutputDrops = util.Str2float64(matches[2]) 77 | } else if matches := inputBytesRegexp.FindStringSubmatch(line); matches != nil { 78 | current.InputBytes = util.Str2float64(matches[1]) 79 | } else if matches := outputBytesRegexp.FindStringSubmatch(line); matches != nil { 80 | current.OutputBytes = util.Str2float64(matches[1]) 81 | } else if matches := inputErrorsRegexp.FindStringSubmatch(line); matches != nil { 82 | current.InputErrors = util.Str2float64(matches[1]) 83 | } else if matches := outputErrorsRegexp.FindStringSubmatch(line); matches != nil { 84 | current.OutputErrors = util.Str2float64(matches[1]) 85 | } else if matches := speedRegexp.FindStringSubmatch(line); matches != nil { 86 | current.Speed = matches[2] + " " + matches[3] 87 | } else if matches := txNXOS.FindStringSubmatch(line); matches != nil { 88 | isRx = false 89 | } else if matches := multiBroadNXOS.FindStringSubmatch(line); matches != nil { 90 | if isRx { 91 | current.InputMulticast = util.Str2float64(matches[1]) 92 | current.InputBroadcast = util.Str2float64(matches[2]) 93 | } 94 | } else if matches := multiBroadIOSXE.FindStringSubmatch(line); matches != nil { 95 | current.InputBroadcast = util.Str2float64(matches[1]) 96 | current.InputMulticast = util.Str2float64(matches[2]) 97 | } else if matches := multiBroadIOS.FindStringSubmatch(line); matches != nil { 98 | current.InputBroadcast = util.Str2float64(matches[1]) 99 | } 100 | } 101 | return append(items, current), nil 102 | } 103 | 104 | // ParseVlans parses cli output and tries to find vlans with related traffic stats 105 | func (c *interfaceCollector) ParseVlans(ostype string, output string) ([]Interface, error) { 106 | if ostype != rpc.IOSXE { 107 | return nil, errors.New("'show vlans' is not implemented for " + ostype) 108 | } 109 | items := []Interface{} 110 | deviceNameRegexp, _ := regexp.Compile(`^([a-zA-Z0-9\/-]+\.[a-zA-Z0-9\/-]+) \(:?\d+\).*$`) 111 | inputBytesRegexp, _ := regexp.Compile(`^\s+Total \d+ packets, (\d+) bytes input.*$`) 112 | outputBytesRegexp, _ := regexp.Compile(`^\s+Total \d+ packets, (\d+) bytes output.*$`) 113 | 114 | current := Interface{} 115 | lines := strings.Split(output, "\n") 116 | for _, line := range lines { 117 | if matches := deviceNameRegexp.FindStringSubmatch(line); matches != nil { 118 | if current != (Interface{}) { 119 | items = append(items, current) 120 | } 121 | current = Interface{ 122 | Name: matches[1], 123 | } 124 | } 125 | if current == (Interface{}) { 126 | continue 127 | } 128 | if matches := inputBytesRegexp.FindStringSubmatch(line); matches != nil { 129 | current.InputBytes = util.Str2float64(matches[1]) 130 | } else if matches := outputBytesRegexp.FindStringSubmatch(line); matches != nil { 131 | current.OutputBytes = util.Str2float64(matches[1]) 132 | } 133 | } 134 | return append(items, current), nil 135 | } 136 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 16 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 17 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 18 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 19 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 20 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 21 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 22 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 23 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 24 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 25 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 26 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 27 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 28 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 29 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 30 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 31 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 32 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 33 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 34 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 35 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 36 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 37 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 38 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 39 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 40 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= 41 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 42 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 43 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 44 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 45 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 46 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= 47 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 48 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 49 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 50 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 51 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 52 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 53 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 54 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 55 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 56 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 57 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 58 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 59 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 60 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 61 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 62 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 63 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 64 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 65 | github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= 66 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 67 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 68 | github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= 69 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 70 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 71 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 72 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 73 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 74 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 75 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 76 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 77 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 78 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 79 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 80 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 81 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 82 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 83 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 84 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 85 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 86 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 87 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 88 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 89 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 90 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 91 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 92 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 93 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 94 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 95 | github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= 96 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 97 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 98 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 99 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 100 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 101 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 102 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 103 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 104 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 105 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 106 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 107 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 108 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 109 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 110 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 111 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 112 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 113 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 114 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 115 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 116 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 117 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 118 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 119 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 120 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 121 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 122 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 123 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 124 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 125 | github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= 126 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 127 | github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 128 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 129 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 130 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 131 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 132 | github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= 133 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 134 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 135 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 136 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 137 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 138 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 139 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 140 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 141 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 142 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 143 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 144 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 145 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 146 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 147 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 148 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 149 | github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 150 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 151 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 152 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 153 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 154 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 155 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 156 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 157 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 158 | github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= 159 | github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= 160 | github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= 161 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 162 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 163 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 164 | github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= 165 | github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 166 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 167 | github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= 168 | github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= 169 | github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= 170 | github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= 171 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 172 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 173 | github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= 174 | github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= 175 | github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= 176 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 177 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 178 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 179 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 180 | github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= 181 | github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= 182 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 183 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 184 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 185 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 186 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 187 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 188 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 189 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 190 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 191 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 192 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 193 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 194 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 195 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 196 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 197 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 198 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 199 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 200 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 201 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 202 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= 203 | golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 204 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 205 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 206 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 207 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 208 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 209 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 210 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 211 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 212 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 213 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 214 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 215 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 216 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 217 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 218 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 219 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 220 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 221 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 222 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 223 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 224 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 225 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 226 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 227 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 228 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 229 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 230 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 231 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 232 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 233 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 234 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 235 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 236 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 237 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 238 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 239 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 240 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 241 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 242 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 243 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 244 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 245 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 246 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 247 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 248 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 249 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 250 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 251 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 252 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 253 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 254 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 255 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 256 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 257 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 258 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 259 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 260 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 261 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 262 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 263 | golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 264 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 265 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 266 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 267 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 268 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 269 | golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 270 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 271 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 272 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 273 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 274 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 275 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 276 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 277 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 278 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 279 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 280 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 281 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 282 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 283 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 284 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 285 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 286 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 287 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 288 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 289 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 290 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 291 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 292 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 293 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 294 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 295 | golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 296 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 297 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 298 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 299 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 300 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 301 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 302 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 303 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 304 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 305 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 306 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 307 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 308 | golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 309 | golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 310 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 311 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 312 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 313 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 314 | golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40 h1:JWgyZ1qgdTaF3N3oxC+MdTV7qvEEgHo3otj+HB5CM7Q= 315 | golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 316 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= 317 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 318 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 319 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 320 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 321 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 322 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 323 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 324 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 325 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 326 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 327 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 328 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 329 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 330 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 331 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 332 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 333 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 334 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 335 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 336 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 337 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 338 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 339 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 340 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 341 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 342 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 343 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 344 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 345 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 346 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 347 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 348 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 349 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 350 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 351 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 352 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 353 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 354 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 355 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 356 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 357 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 358 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 359 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 360 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 361 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 362 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 363 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 364 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 365 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 366 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 367 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 368 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 369 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 370 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 371 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 372 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 373 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 374 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 375 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 376 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 377 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 378 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 379 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 380 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 381 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 382 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 383 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 384 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 385 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 386 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 387 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 388 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 389 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 390 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 391 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 392 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 393 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 394 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 395 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 396 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 397 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 398 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 399 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 400 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 401 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 402 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 403 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 404 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 405 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 406 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 407 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 408 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 409 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 410 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 411 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 412 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 413 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 414 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 415 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 416 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 417 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 418 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 419 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 420 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 421 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 422 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 423 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 424 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 425 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 426 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 427 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 428 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 429 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 430 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 431 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 432 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 433 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 434 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 435 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 436 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 437 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 438 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 439 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 440 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 441 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 442 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 443 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 444 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 445 | google.golang.org/protobuf v1.26.0-rc.1 h1:7QnIQpGRHE5RnLKnESfDoxm2dTapTZua5a0kS0A+VXQ= 446 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 447 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 448 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 449 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 450 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 451 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 452 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 453 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 454 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 455 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 456 | gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 457 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 458 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 459 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 460 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 461 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 462 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 463 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 464 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 465 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 466 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 467 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 468 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 469 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 470 | --------------------------------------------------------------------------------