Skip to content

Commit

Permalink
Merge pull request #15 from yubiuser/development
Browse files Browse the repository at this point in the history
v1.1.0
  • Loading branch information
yubiuser authored Sep 6, 2023
2 parents 7910baa + 83e9924 commit 6d89f93
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 65 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ghcr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
uses: actions/[email protected]

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.9.1
uses: docker/setup-buildx-action@v2.10.0

- name: Extract metadata (tags, labels) for Docker
id: meta
Expand Down
13 changes: 10 additions & 3 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Monitor Docker events and send push notifications for each event.

- Small memory and CPU footprint
- Pushover integration
- Gotify integration
- Filter events

## Background
Expand Down Expand Up @@ -45,11 +46,14 @@ services:
- /etc/localtime:/etc/localtime:ro
restart: unless-stopped
environment:
PUSHOVER: true
PUSHOVER: false
PUSHOVER_USER: 'USER'
PUSHOVER_APITOKEN: 'TOKEN'
GOTIFY: false
GOTIFY_URL: 'URL'
GOTIFY_TOKEN: 'TOKEN'
FILTER: 'event=start,event=stop,type=container'
PUSHOVER_DELAY: '500ms'
DELAY: '500ms'
LOG_LEVEL: 'info'
```
Expand All @@ -75,6 +79,9 @@ Configurations can use the CLI flags or environment variables. The table below o
| `--pushover` | `PUSHOVER` | `false` |Enable/Disable Pushover notification|
| `--pushoverapitoken` | `PUSHOVER_APITOKEN` | `""` | |
| `--pushoveruserkey` | `PUSHOVER_USER` | `""` | |
| `--pushoverdelay` | `PUSHOVER_DELAY` | `500ms` |Delay befor processing next event. Can be useful if messages arrive in wrong order |
| `--delay` | `DELAY` | `500ms` |Delay befor processing next event. Can be useful if messages arrive in wrong order |
| `--gotify` | `GOTIFY` | `false` |Enable/Disable Gotify notification|
| `--gotifyurl` | `GOTIFY_URL` | `""` | |
| `--gotifytoken` | `GOTIFY_TOKEN` | `""` | |
| `--filter` | `FILTER` | `""` | Filter events. Uses the same filters as `docker events` (see [here](https://docs.docker.com/engine/reference/commandline/events/#filter)) |
| `--loglevel` | `LOG_LEVEL` | `"info"`| Use `debug` for more verbose logging` |
7 changes: 5 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ services:
- /etc/localtime:/etc/localtime:ro
restart: unless-stopped
environment:
PUSHOVER: true
PUSHOVER: false
PUSHOVER_USER: 'USER'
PUSHOVER_APITOKEN: 'TOKEN'
GOTIFY: false
GOTIFY_URL: 'URL'
GOTIFY_TOKEN: 'TOKEN'
FILTER: 'event=start,event=stop,type=container'
PUSHOVER_DELAY: '500ms'
DELAY: '500ms'
LOG_LEVEL: 'info'

141 changes: 97 additions & 44 deletions src/docker-event-monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ package main
import (
"context"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
"sync"
"time"

"github.com/alexflint/go-arg"
Expand All @@ -23,32 +27,36 @@ type args struct {
Pushover bool `arg:"env:PUSHOVER" default:"false" help:"Enable/Disable Pushover Notification (True/False)"`
PushoverAPIToken string `arg:"env:PUSHOVER_APITOKEN" help:"Pushover's API Token/Key"`
PushoverUserKey string `arg:"env:PUSHOVER_USER" help:"Pushover's User Key"`
PushoverDelay time.Duration `arg:"env:PUSHOVER_DELAY" default:"500ms" help:"Delay before next Pushover message is send"`
Gotify bool `arg:"env:GOTIFY" default:"false" help:"Enable/Disable Gotify Notification (True/False)"`
GotifyURL string `arg:"env:GOTIFY_URL" help:"URL of your Gotify server"`
GotifyToken string `arg:"env:GOTIFY_TOKEN" help:"Gotify's App Token"`
Delay time.Duration `arg:"env:DELAY" default:"500ms" help:"Delay before next message is send"`
FilterStrings []string `arg:"env:FILTER,--filter,separate" help:"Filter docker events using Docker syntax."`
Filter map[string][]string `arg:"-"`
LogLevel string `arg:"env:LOG_LEVEL" default:"info" help:"Set log level. Use debug for more logging."`
}

func main() {
args := parseArgs()
var wg sync.WaitGroup

log.Infof("Starting docker event monitor")

if args.Pushover {
log.Infof("Using Pushover API Token %s", args.PushoverAPIToken)
log.Infof("Using Pushover User Key %s", args.PushoverUserKey)
log.Infof("Using Pushover delay of %v", args.PushoverDelay)
} else {
log.Info("Pushover notification disabled")
}

if args.Pushover {
sendPushover(args, time.Now().Format("02-01-2006 15:04:05"), "Starting docker event monitor")
if args.Gotify {
log.Infof("Using Gotify APP Token %s", args.GotifyToken)
log.Infof("Using Gotify URL %s", args.GotifyURL)
} else {
log.Info("Gotify notification disabled")
}

cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
log.Panic(err)
if args.Delay > 0 {
log.Infof("Using delay of %v", args.Delay)
}

filterArgs := filters.NewArgs()
Expand All @@ -57,31 +65,77 @@ func main() {
filterArgs.Add(key, value)
}
}

log.Debugf("filterArgs = %v", filterArgs)

// receives events
wg.Add(2)
if args.Pushover {
go sendPushover(&args, time.Now().Format("02-01-2006 15:04:05"), "Starting docker event monitor", &wg)
}
if args.Gotify {
go sendGotify(&args, time.Now().Format("02-01-2006 15:04:05"), "Starting docker event monitor", &wg)
}

cli, err := client.NewClientWithOpts(client.FromEnv)
if err != nil {
log.Fatal(err)
}

// receives events from the channel
event_chan, errs := cli.Events(context.Background(), types.EventsOptions{Filters: filterArgs})

for {
select {
case err := <-errs:
log.Panic(err)
log.Fatal(err)
case event := <-event_chan:
processEvent(args, event)
processEvent(&args, &event, &wg)
// Adding a small configurable delay here
// Sometimes events are pushed through the channel really quickly, but
// they arrive on the Pushover clients in wrong order (probably due to message delivery time)
// they arrive on the clients in wrong order (probably due to message delivery time)
// This affects mostly Pushover
// Consuming the events with a small delay solves the issue
if args.Pushover {
time.Sleep(args.PushoverDelay)
if args.Delay > 0 {
time.Sleep(args.Delay)
}
}
}
}

func sendPushover(args args, message, title string) bool {
func sendGotify(args *args, message, title string, wg *sync.WaitGroup) {
defer wg.Done()

response, err := http.PostForm(args.GotifyURL+"/message?token="+args.GotifyToken,
url.Values{"message": {message}, "title": {title}})
if err != nil {
log.Error(err)
return
}

defer response.Body.Close()

statusCode := response.StatusCode

body, err := io.ReadAll(response.Body)
if err != nil {
log.Error(err)
return
}

log.Debugf("Gotify response statusCode: %d", statusCode)
log.Debugf("Gotify response body: %s", string(body))

// Log non successfull status codes
if statusCode == 200 {
log.Debugf("Gotify message delivered")
} else {
log.Errorf("Pushing gotify message failed.")
log.Errorf("Gotify response body: %s", string(body))
}

}

func sendPushover(args *args, message, title string, wg *sync.WaitGroup) {
defer wg.Done()
// Create a new pushover app with an API token
app := pushover.New(args.PushoverAPIToken)

Expand All @@ -94,7 +148,8 @@ func sendPushover(args args, message, title string) bool {
// Send the message to the recipient
response, err := app.SendMessage(pushmessage, recipient)
if err != nil {
log.Panic(err)
log.Error(err)
return
}
if response != nil {
log.Debugf("%s", response)
Expand All @@ -103,22 +158,23 @@ func sendPushover(args args, message, title string) bool {
if (*response).Status == 1 {
// Pushover returns 1 if the message request to the API was valid
// https://pushover.net/api#response
return true
log.Debugf("Pushover message delivered")
return
}
// default to false if response Status !=1
return false

// if response Status !=1
log.Errorf("Pushover message not delivered")

}

func processEvent(args args, event events.Message) {
func processEvent(args *args, event *events.Message, wg *sync.WaitGroup) {
// the Docker Events endpoint will return a struct events.Message
// https://pkg.go.dev/github.com/docker/docker/api/types/events#Message

var message string

// if logging level is Debug or higher, log the event
if log.IsLevelEnabled(log.DebugLevel) {
log.Debugf("%#v", event)
}
// if logging level is Debug, log the event
log.Debugf("%#v", event)

//event_timestamp := time.Unix(event.Time, 0).Format("02-01-2006 15:04:05")

Expand All @@ -128,42 +184,39 @@ func processEvent(args args, event events.Message) {
ID = strings.TrimPrefix(event.Actor.ID, "sha256:")[:8] //remove prefix + limit ID legth
}
if len(event.Actor.Attributes["image"]) > 0 {
// trim string from left until the final '/'
index := strings.LastIndex(event.Actor.Attributes["image"], "/")
if index != -1 {
image = event.Actor.Attributes["image"][index+1:]
} else {
image = event.Actor.Attributes["image"]
}
image = event.Actor.Attributes["image"]
}

// Log and prepare Pushover message
// Prepare message
if len(ID) == 0 {
if len(image) == 0 {
message = fmt.Sprintf("Object '%s' reported: %s", cases.Title(language.English, cases.Compact).String(event.Type), event.Action)
log.Info(message)
} else {
message = fmt.Sprintf("Object '%s' from image %s reported: %s", cases.Title(language.English, cases.Compact).String(event.Type), image, event.Action)
log.Info(message)
}
} else {
if len(image) == 0 {
message = fmt.Sprintf("Object '%s' with ID %s reported: %s", cases.Title(language.English, cases.Compact).String(event.Type), ID, event.Action)
log.Info(message)
} else {
message = fmt.Sprintf("Object '%s' with ID %s from image %s reported: %s", cases.Title(language.English, cases.Compact).String(event.Type), ID, image, event.Action)
log.Info(message)
}
}

log.Info(message)

// Sending messages to different services as goroutines concurrently
// Adding a wait group here to delay execution until all functions return,
// otherwise the delay in main() would not use its full time

wg.Add(2)
if args.Pushover {
delivered := sendPushover(args, message, "New Docker Event")
if delivered {
log.Debugf("Message delivered")
} else {
log.Warnf("Message not delivered")
}
go sendPushover(args, message, "New Docker Event", wg)
}

if args.Gotify {
go sendGotify(args, message, "New Docker Event", wg)
}
wg.Wait()
}

func parseArgs() args {
Expand Down Expand Up @@ -192,7 +245,7 @@ func configureLogger(LogLevel string) {
if l, err := log.ParseLevel(LogLevel); err == nil {
log.SetLevel(l)
} else {
log.Panic(err)
log.Fatal(err)
}

// Output to stdout instead of the default stderr
Expand Down
10 changes: 5 additions & 5 deletions src/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ go 1.21.0

require (
github.com/alexflint/go-arg v1.4.3
github.com/docker/docker v24.0.5+incompatible
github.com/docker/docker v24.0.6+incompatible
github.com/gregdel/pushover v1.2.1
github.com/sirupsen/logrus v1.9.3
golang.org/x/text v0.12.0
golang.org/x/text v0.13.0
)

require (
Expand All @@ -24,9 +24,9 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/stretchr/testify v1.8.4 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.14.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/net v0.15.0 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.12.0 // indirect
golang.org/x/tools v0.13.0 // indirect
gotest.tools/v3 v3.5.0 // indirect
)
20 changes: 10 additions & 10 deletions src/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v24.0.5+incompatible h1:WmgcE4fxyI6EEXxBRxsHnZXrO1pQ3smi0k/jho4HLeY=
github.com/docker/docker v24.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v24.0.6+incompatible h1:hceabKCtUgDqPu+qm0NgsaXf28Ljf4/pWFL7xjWWDgE=
github.com/docker/docker v24.0.6+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
Expand Down Expand Up @@ -58,8 +58,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand All @@ -69,20 +69,20 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
Expand Down

0 comments on commit 6d89f93

Please sign in to comment.