initial commit

This commit is contained in:
Daniel Chote 2019-10-15 11:40:10 -04:00
parent 30ac0d6bb0
commit ae54d666ca
12 changed files with 557 additions and 1 deletions

61
.github/workflows/main.yml vendored Normal file

@ -0,0 +1,61 @@
name: Build and Test
on:
push:
branches:
- master
pull_request:
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.13
- name: Check out code
uses: actions/checkout@v1
- name: Lint Go Code
run: |
export PATH=$PATH:$(go env GOPATH)/bin # temporary fix. See https://github.com/actions/setup-go/issues/14
go get -u golang.org/x/lint/golint
make lint
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.13
- name: Check out code
uses: actions/checkout@v1
- name: Run Unit tests
run: |
export PATH=$PATH:$(go env GOPATH)/bin
make test-coverage
build:
name: Build
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- name: Set up Go
uses: actions/setup-go@v1
with:
go-version: 1.13
- name: Check out code
uses: actions/checkout@v1
- name: Build Everything
run: |
export PATH=$PATH:$(go env GOPATH)/bin
make build

25
.github/workflows/release.yml vendored Normal file

@ -0,0 +1,25 @@
name: Release
on:
create:
tags:
- v*
jobs:
release:
name: Publish Release
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v1
- name: Validates GO releaser config
uses: docker://goreleaser/goreleaser:latest
with:
args: check
- name: Create release on GitHub
uses: docker://goreleaser/goreleaser:latest
with:
args: release
env:
GITHUB_TOKEN: ${{secrets.GORELEASER_GITHUB_TOKEN}}

2
.gitignore vendored Normal file

@ -0,0 +1,2 @@
build
.DS_Store

16
.goreleaser.yml Normal file

@ -0,0 +1,16 @@
env:
- GO111MODULE=on
before:
hooks:
- go mod tidy
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- darwin
goarch:
- arm
- arm64
- amd64
- 386

45
Makefile Normal file

@ -0,0 +1,45 @@
PROJECT_NAME := "snmp-mqtt"
PKG := "github.com/dchote/$(PROJECT_NAME)"
PKG_LIST := $(shell go list ${PKG}/... | grep -v /vendor/)
GO_FILES := $(shell find . -name '*.go' | grep -v /vendor/ | grep -v _test.go)
.PHONY: all dep lint vet test test-coverage build clean
all: build
dep: ## Get the dependencies
@echo Installing dependencies
@go mod download
lint: ## Lint Golang files
@golint -set_exit_status ${PKG_LIST}
vet: ## Run go vet
@go vet ${PKG_LIST}
test: ## Run unittests
@go test -short ${PKG_LIST}
test-coverage: ## Run tests with coverage
@go test -short -coverprofile cover.out -covermode=atomic ${PKG_LIST}
@cat cover.out >> coverage.txt
build: dep ## Build the binary file
@echo Building native binary
@go build -i -o build/snmp-mqtt $(PKG)
linux: build
@echo Building Linux binary
@env CC=x86_64-linux-musl-gcc GOOS=linux GOARCH=amd64 CGO_ENABLED=1 go build -ldflags "-linkmode external -extldflags -static" -o build/snmp-mqtt
raspi: build
@echo Building Rasperry Pi Linux binary
@env GOOS=linux GOARCH=arm GOARM=6 CGO_ENABLED=0 go build -o build/snmp-mqtt
clean: ## Remove previous build
@rm -f $(PROJECT_NAME)/build
help: ## Display this help screen
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

@ -1 +1,41 @@
# snmp-mqtt
# snmp-mqtt
A simple go app that reads SNMP values and publishes it to the specified MQTT endpoint at the specified interval.
Please download the precompiled binary from the releases page: https://github.com/dchote/snmp-mqtt/releases
```
Usage: snmp-mqtt [options]
Options:
--endpoints_map=<endpoints_map> SNMP Endpoints Map File [default: ./endpoints.json]
--server=<server> MQTT server host/IP [default: 127.0.0.1]
--port=<port> MQTT server port [default: 1883]
--topic=<topic> MQTT topic prefix [default: snmp]
--clientid=<clientid> MQTT client identifier [default: snmp]
--interval=<interval> Poll interval (seconds) [default: 5]
-h, --help Show this screen.
-v, --version Show version.
```
An example endpoints.json file:
```
{
"snmpEndpoints": [
{
"endpoint": "172.18.0.1",
"community": "public",
"oidTopics": [
{
"oid": ".1.3.6.1.2.1.31.1.1.1.6.4",
"topic": "router/bytesIn"
},
{
"oid": ".1.3.6.1.2.1.31.1.1.1.10.4",
"topic": "router/bytesOut"
}
]
}
]
}
```

66
config/config.go Normal file

@ -0,0 +1,66 @@
package config
import (
"encoding/json"
"os"
"strconv"
)
type OIDTopicObject struct {
OID string `json:"oid"`
Topic string `json:"topic"`
}
type SNMPEndpointObject struct {
Endpoint string `json:"endpoint"`
Community string `json:"community"`
OIDTopics []OIDTopicObject `json:"oidTopics"`
}
// SNMPConfig basic config
type SNMPMapObject struct {
SNMPEndpoints []SNMPEndpointObject `json:"snmpEndpoints"`
}
var (
// SNMPMap is the loaded JSON configuration
SNMPMap *SNMPMapObject
// Server is the MQTT server address
Server string
// Port is the MQTT server listen port
Port int
// ClientID is how the name of the client
ClientID string
// TopicPrefix is just that, a prefix for the presented data keys
TopicPrefix string
// Interval is the poll interval in seconds
Interval int
)
// ConnectionString returns the MQTT connection string
func ConnectionString() string {
return "tcp://" + Server + ":" + strconv.Itoa(Port)
}
// LoadMap loads the file in to the struct
func LoadMap(file string) error {
configFile, err := os.Open(file)
defer configFile.Close()
if err != nil {
return err
}
jsonParser := json.NewDecoder(configFile)
err = jsonParser.Decode(&SNMPMap)
if err != nil {
return err
}
return nil
}

84
endpoints.json Normal file

@ -0,0 +1,84 @@
{
"snmpEndpoints": [
{
"endpoint": "172.18.0.1",
"community": "public",
"oidTopics": [
{
"oid": ".1.3.6.1.2.1.31.1.1.1.6.4",
"topic": "router/bytesIn"
},
{
"oid": ".1.3.6.1.2.1.31.1.1.1.10.4",
"topic": "router/bytesOut"
}
]
},
{
"endpoint": "172.18.0.50",
"community": "public",
"oidTopics": [
{
"oid": ".1.3.6.1.4.1.318.1.1.12.2.3.1.1.2.1",
"topic": "rackPDU/totalLoad"
},
{
"oid": ".1.3.6.1.4.1.318.1.1.12.1.16.0",
"topic": "rackPDU/watts"
}
]
},
{
"endpoint": "172.18.0.51",
"community": "public",
"oidTopics": [
{
"oid": ".1.3.6.1.4.1.318.1.1.1.3.2.1.0",
"topic": "rackUPS/lineVoltage"
},
{
"oid": ".1.3.6.1.4.1.318.1.1.1.4.2.4.0",
"topic": "rackUPS/outputCurrent"
},
{
"oid": ".1.3.6.1.4.1.318.1.1.1.2.2.1.0",
"topic": "rackUPS/batteryCapacity"
},
{
"oid": ".1.3.6.1.4.1.318.1.1.1.2.2.3.0",
"topic": "rackUPS/batteryRuntime"
},
{
"oid": ".1.3.6.1.4.1.318.1.1.1.2.2.2.0",
"topic": "rackUPS/batteryTemperature"
}
]
},
{
"endpoint": "172.18.0.52",
"community": "public",
"oidTopics": [
{
"oid": ".1.3.6.1.4.1.318.1.1.1.3.2.1.0",
"topic": "printerUPS/lineVoltage"
},
{
"oid": ".1.3.6.1.4.1.318.1.1.1.4.2.4.0",
"topic": "printerUPS/outputCurrent"
},
{
"oid": ".1.3.6.1.4.1.318.1.1.1.2.2.1.0",
"topic": "printerUPS/batteryCapacity"
},
{
"oid": ".1.3.6.1.4.1.318.1.1.1.2.2.3.0",
"topic": "printerUPS/batteryRuntime"
},
{
"oid": ".1.3.6.1.4.1.318.1.1.1.2.2.2.0",
"topic": "printerUPS/batteryTemperature"
}
]
}
]
}

12
go.mod Normal file

@ -0,0 +1,12 @@
module github.com/dchote/snmp-mqtt
go 1.13
require (
github.com/aleasoluciones/goaleasoluciones v0.0.0-20190802084519-19690e2580be // indirect
github.com/aleasoluciones/gosnmpquerier v0.0.0-20190802084245-be620504e4c1
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 // indirect
github.com/eclipse/paho.mqtt.golang v1.2.0
github.com/soniah/gosnmp v1.22.0
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 // indirect
)

21
go.sum Normal file

@ -0,0 +1,21 @@
github.com/aleasoluciones/goaleasoluciones v0.0.0-20190802084519-19690e2580be h1:+8uDL4/VO0/GuAPW0x7l/mGFS6ACAqphmzL/k/69T7s=
github.com/aleasoluciones/goaleasoluciones v0.0.0-20190802084519-19690e2580be/go.mod h1:dPDme2W0OahZUhqg9YFKeTvA7EeuLaT9jXWhiTsNIAE=
github.com/aleasoluciones/gosnmpquerier v0.0.0-20190802084245-be620504e4c1 h1:14GLmz3g2hEyJsEVwd/aQz9GDtmf3/IYNi03twvSWxc=
github.com/aleasoluciones/gosnmpquerier v0.0.0-20190802084245-be620504e4c1/go.mod h1:UAyt3+5oUrjn/x3W1YJT7259PP6NFGokAm1h5h5B9NE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/eclipse/paho.mqtt.golang v1.2.0 h1:1F8mhG9+aO5/xpdtFkW4SxOJB67ukuDC3t2y2qayIX0=
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/soniah/gosnmp v1.22.0 h1:jVJi8+OGvR+JHIaZKMmnyNP0akJd2vEgNatybwhZvxg=
github.com/soniah/gosnmp v1.22.0/go.mod h1:DuEpAS0az51+DyVBQwITDsoq4++e3LTNckp2GoasF2I=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 h1:p9xBe/w/OzkeYVKm234g55gMdD1nSIooTir5kV11kfA=
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

86
snmp-mqtt.go Normal file

@ -0,0 +1,86 @@
package main
import (
"log"
"os"
"os/signal"
"syscall"
"github.com/dchote/snmp-mqtt/config"
"github.com/dchote/snmp-mqtt/snmp"
"github.com/docopt/docopt-go"
)
var exitChan = make(chan int)
// VERSION beause...
const VERSION = "0.0.1"
func cliArguments() {
usage := `
Usage: snmp-mqtt [options]
Options:
--endpoints_map=<endpoints_map> SNMP Endpoints Map File [default: ./endpoints.json]
--server=<server> MQTT server host/IP [default: 127.0.0.1]
--port=<port> MQTT server port [default: 1883]
--topic=<topic> MQTT topic prefix [default: snmp]
--clientid=<clientid> MQTT client identifier [default: snmp]
--interval=<interval> Poll interval (seconds) [default: 5]
-h, --help Show this screen.
-v, --version Show version.
`
args, _ := docopt.ParseArgs(usage, os.Args[1:], VERSION)
mapFile, _ := args.String("--endpoints_map")
err := config.LoadMap(mapFile)
if err != nil {
log.Println(err)
log.Fatal("error opening " + mapFile)
}
config.Server, _ = args.String("--server")
config.Port, _ = args.Int("--port")
config.TopicPrefix, _ = args.String("--topic")
config.ClientID, _ = args.String("--clientid")
config.Interval, _ = args.Int("--interval")
log.Printf("server: %s, port: %d, topic prefix: %s, client identifier: %s, poll interval: %d", config.Server, config.Port, config.TopicPrefix, config.ClientID, config.Interval)
}
// sigChannelListen basic handlers for inbound signals
func sigChannelListen() {
signalChan := make(chan os.Signal, 1)
code := 0
signal.Notify(signalChan, os.Interrupt)
signal.Notify(signalChan, os.Kill)
signal.Notify(signalChan, syscall.SIGTERM)
select {
case sig := <-signalChan:
log.Printf("Received signal %s. shutting down", sig)
case code = <-exitChan:
switch code {
case 0:
log.Println("Shutting down")
default:
log.Println("*Shutting down")
}
}
os.Exit(code)
}
func main() {
cliArguments()
// catch signals
go sigChannelListen()
// run sensor poll loop
snmp.Init()
os.Exit(0)
}

98
snmp/snmp.go Normal file

@ -0,0 +1,98 @@
package snmp
import (
"fmt"
"log"
"strings"
"sync"
"time"
"github.com/dchote/snmp-mqtt/config"
"github.com/eclipse/paho.mqtt.golang"
"github.com/soniah/gosnmp"
)
var ()
// Init contains the generic read/publish loop
func Init() {
opts := mqtt.NewClientOptions().AddBroker(config.ConnectionString()).SetClientID(config.ClientID)
opts.SetKeepAlive(2 * time.Second)
opts.SetPingTimeout(1 * time.Second)
client := mqtt.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
panic(token.Error())
}
var wg sync.WaitGroup
for {
wg.Add(1)
go func() {
defer wg.Done()
for _, endpoint := range config.SNMPMap.SNMPEndpoints {
log.Println("Polling endpoint " + endpoint.Endpoint)
snmp := gosnmp.GoSNMP{}
snmp.Target = endpoint.Endpoint
snmp.Port = 161
snmp.Version = gosnmp.Version2c
snmp.Community = endpoint.Community
snmp.Timeout = time.Duration(5 * time.Second)
err := snmp.Connect()
if err != nil {
log.Fatal("SNMP Connect err: %v\n", err)
}
oids := []string{}
for _, oidTopic := range endpoint.OIDTopics {
oids = append(oids, oidTopic.OID)
}
result, err := snmp.Get(oids)
if err != nil {
log.Printf("error in Get: %s", err)
} else {
for _, variable := range result.Variables {
for _, oidTopic := range endpoint.OIDTopics {
if strings.Compare(oidTopic.OID, variable.Name) == 0 {
convertedValue := ""
switch variable.Type {
case gosnmp.OctetString:
convertedValue = string(variable.Value.([]byte))
default:
convertedValue = fmt.Sprintf("%d", gosnmp.ToBigInt(variable.Value))
}
log.Printf("%s = %s", oidTopic.Topic, convertedValue)
token := client.Publish(config.TopicPrefix+"/"+oidTopic.Topic, 0, false, convertedValue)
token.Wait()
if token.Error() != nil {
log.Fatal(token.Error())
}
}
}
}
}
snmp.Conn.Close()
}
}()
time.Sleep(time.Duration(config.Interval) * time.Second)
}
wg.Wait()
client.Disconnect(250)
time.Sleep(1 * time.Second)
}