Skip to content

Commit

Permalink
Merge pull request #77 from yubiuser/development
Browse files Browse the repository at this point in the history
v2.2.0
  • Loading branch information
yubiuser authored Feb 18, 2024
2 parents afe2f8f + e73eb7c commit 9a163f1
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 20 deletions.
1 change: 1 addition & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# syntax=docker/dockerfile:1
ARG alpine_version=3.19
ARG golang_version=1.21

Expand Down
Binary file modified .github/pushover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions .github/workflows/ghcr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ jobs:
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/[email protected].0
uses: actions/[email protected].1
with:
name: digests-${{ env.PLATFORM_PAIR }}
path: /tmp/digests/*
Expand All @@ -116,7 +116,7 @@ jobs:
packages: write
steps:
- name: Download digests
uses: actions/[email protected].1
uses: actions/[email protected].2
with:
path: /tmp/digests
pattern: digests-*
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# syntax=docker/dockerfile:1
ARG alpine_version=3.19
ARG golang_version=1.21

Expand Down
18 changes: 17 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ services:
MAIL_PASSWORD: 'PASSWORD'
MAIL_PORT: 587
MAIL_HOST: '[email protected]'
FILTER: 'event=start,event=stop,type=container'
FILTER: 'type=container'
EXCLUDE: 'Action=exec_start,Action=exec_die,Action=exec_create'
DELAY: '500ms'
LOG_LEVEL: 'info'
SERVER_TAG: ''
Expand Down Expand Up @@ -100,5 +101,20 @@ Configurations can use the CLI flags or environment variables. The table below o
| `--mailport` | `MAIL_PORT` | `587` | |
| `--mailhost` | `MAIL_HOST` | `""` | `[email protected]` |
| `--filter` | `FILTER` | `""` | Filter events. Uses the same filters as `docker events` (see [here](https://docs.docker.com/engine/reference/commandline/events/#filter)) |
| `--exclude` | `EXCLUDE` | `""` | Exclude events from being reported |
| `--loglevel` | `LOG_LEVEL` | `"info"`| Use `debug` for more verbose logging |
| `--servertag` | `SERVER_TAG` | `""` | Prefix to include in the title of notifications. Useful when running docker-event-monitors on multiple machines |

### Filter and exclude events

Docker Event Monitor offers two options that sound alike, but aren't: `Filter` and `Exclude`.
By default, the docker system event stream will contain **all** events. The `filter` option is a docker built-in function that allows filtering certain events from the stream. It's a **positive** filter only, meaning it defines which events will pass the filter. The possible filters and syntax are described [here](https://docs.docker.com/engine/reference/commandline/events/#filter).

However, docker has no native support for **negative** filter (let all events pass, except those defined) - so I added it. To distingush it from postive filters, this option is named `exclude`.
Based on how it is implemented, **exclusion happens after filtering**. Together you can create configurations like filtering events of type container, but exclude reporting for a specific container or certain actions.

The syntax for exclusion is also `key=value`. But as the exclusion happens on the data contained in the reported event, the `key`s are different from those used for `filtering`. E.g. instead of `event`, `Action` is used. To figure out which keys to use, it's best to enable debug logging and carefully inspect the event's data structure. A typical container event looks like

```
{Status:"start", ID:"b4a2a54c4487ddc0bbae006e48ae970d4b2fa4b9fd2bef390d8875cb6158c888", From:"squidfunk/mkdocs-material", Type:"container", Action:"start", Actor:events.Actor{ID:"b4a2a54c4487ddc0bbae006e48ae970d4b2fa4b9fd2bef390d8875cb6158c888", Attributes:map[string]string{"com.docker.compose.config-hash":"cd464ac038ddc9ee7a53599aaa9db6a85a01683a9a08a749582d0c0b8c0a595d", "com.docker.compose.container-number":"1", "com.docker.compose.depends_on":"", "com.docker.compose.image":"sha256:feb8ba83cb7272046551c69a58ec03ecda2306410a07844d22c166e810034aa6", "com.docker.compose.oneoff":"False", "com.docker.compose.project":"mkdocs-material", "com.docker.compose.project.config_files":"/home/pi/docker/mkdocs-material/docker-compose.yml", "com.docker.compose.project.working_dir":"/home/pi/docker/mkdocs-material", "com.docker.compose.service":"mkdocs-material", "com.docker.compose.version":"2.24.5", "image":"squidfunk/mkdocs-material", "name":"mkdocs-material", "org.opencontainers.image.created":"2024-02-10T06:18:18.743Z", "org.opencontainers.image.description":"Documentation that simply works", "org.opencontainers.image.licenses":"MIT", "org.opencontainers.image.revision":"a6286ef3ac3407e8b6c985cf0571fc0e2caa6f5b", "org.opencontainers.image.source":"https://github.com/squidfunk/mkdocs-material", "org.opencontainers.image.title":"mkdocs-material", "org.opencontainers.image.url":"https://github.com/squidfunk/mkdocs-material", "org.opencontainers.image.version":"9.5.9"}}, Scope:"local", Time:1708201602, TimeNano:1708201602805856956}
```
132 changes: 123 additions & 9 deletions src/docker-event-monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"fmt"
"io"
"net/http"
"net/smtp"
Expand All @@ -18,6 +19,7 @@ import (
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/gregdel/pushover"
"github.com/oleiade/reflections"

"github.com/rs/zerolog"

Expand All @@ -42,6 +44,8 @@ type args struct {
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:"-"`
ExcludeStrings []string `arg:"env:EXCLUDE,--exclude,separate" help:"Exclude docker events using Docker syntax."`
Exclude map[string][]string `arg:"-"`
LogLevel string `arg:"env:LOG_LEVEL" default:"info" help:"Set log level. Use debug for more logging."`
ServerTag string `arg:"env:SERVER_TAG" help:"Prefix to include in the title of notifications. Useful when running docker-event-monitors on multiple machines."`
Version bool `arg:"-v" help:"Print version information."`
Expand All @@ -57,8 +61,8 @@ var glb_arguments args
var (
version string = "n/a"
commit string = "n/a"
date string
gitdate string
date string = "0"
gitdate string = "0"
branch string = "n/a"
)

Expand Down Expand Up @@ -140,7 +144,8 @@ func main() {
Str("Delay", glb_arguments.Delay.String()).
Str("Loglevel", glb_arguments.LogLevel).
Str("ServerTag", glb_arguments.ServerTag).
Str("Filter", strings.Join(glb_arguments.FilterStrings, " ")),
Str("Filter", strings.Join(glb_arguments.FilterStrings, " ")).
Str("Exclude", strings.Join(glb_arguments.ExcludeStrings, " ")),
).
Dict("version", zerolog.Dict().
Str("Version", version).
Expand Down Expand Up @@ -176,6 +181,16 @@ func main() {
case err := <-errs:
logger.Fatal().Err(err).Msg("")
case event := <-event_chan:
// if logging level is Debug, log the event
logger.Debug().Msgf("%#v", event)

// Check if event should be exlcuded from reporting
if len(glb_arguments.Exclude) > 0 {
logger.Debug().Msg("Performing check for event exclusion")
if excludeEvent(event) {
break //breaks out of the select and waits for the next event to arrive
}
}
processEvent(&event)
}
}
Expand All @@ -185,6 +200,7 @@ func buildStartupMessage(timestamp time.Time) string {
var startup_message_builder strings.Builder

startup_message_builder.WriteString("Docker event monitor started at " + timestamp.Format(time.RFC1123Z) + "\n")
startup_message_builder.WriteString("Docker event monitor version: " + version + "\n")

if glb_arguments.Pushover {
startup_message_builder.WriteString("Notify via Pushover, using API Token " + glb_arguments.PushoverAPIToken + " and user key " + glb_arguments.PushoverUserKey)
Expand Down Expand Up @@ -217,6 +233,18 @@ func buildStartupMessage(timestamp time.Time) string {
startup_message_builder.WriteString("\nServerTag: none")
}

if len(glb_arguments.FilterStrings) > 0 {
startup_message_builder.WriteString("\nFilterStrings: " + strings.Join(glb_arguments.FilterStrings, " "))
} else {
startup_message_builder.WriteString("\nFilterStrings: none")
}

if len(glb_arguments.ExcludeStrings) > 0 {
startup_message_builder.WriteString("\nExcludeStrings: " + strings.Join(glb_arguments.ExcludeStrings, " "))
} else {
startup_message_builder.WriteString("\nExcludeStrings: none")
}

return startup_message_builder.String()
}

Expand Down Expand Up @@ -360,6 +388,77 @@ func sendPushover(message string, title string) {

}

func excludeEvent(event events.Message) bool {
// Checks if any of the exclusion criteria matches the event

var ActorID string
if len(event.Actor.ID) > 0 {
if strings.HasPrefix(event.Actor.ID, "sha256:") {
ActorID = strings.TrimPrefix(event.Actor.ID, "sha256:")[:8] //remove prefix + limit ActorID legth
} else {
ActorID = event.Actor.ID[:8] //limit ActorID legth
}
}

// getting the values of the events struct
// first check if any exclusion key matches a key in the event message
for key, values := range glb_arguments.Exclude {
fieldExists, err := reflections.HasField(event, key)
if err != nil {
logger.Error().Err(err).
Str("ActorID", ActorID).
Str("Key", key).
Msg("Error while checking existence of event field")
}
if fieldExists {
// key matched, check if any value matches
logger.Debug().
Str("ActorID", ActorID).
Msgf("Exclusion key \"%s\" matched, checking values", key)

eventValue, err := reflections.GetField(event, key)
if err != nil {
logger.Error().Err(err).
Str("ActorID", ActorID).
Str("Key", key).
Msg("Error while getting event field's value")
}

logger.Debug().
Str("ActorID", ActorID).
Msgf("Event's value for key \"%s\" is \"%s\"", key, eventValue)

//GetField returns an interface which needs to be converted to string
strEventValue := fmt.Sprintf("%v", eventValue)

for _, value := range values {
// comparing the prefix to be able to filter actions like "exec_XXX: YYYY" which use a
// special, dynamic, syntax
// see https://github.com/moby/moby/blob/bf053be997f87af233919a76e6ecbd7d17390e62/api/types/events/events.go#L74-L81

if strings.HasPrefix(strEventValue, value) {
logger.Debug().
Str("ActorID", ActorID).
Msgf("Event excluded based on exclusion setting \"%s=%s\"", key, value)
return true
}
}
logger.Debug().
Str("ActorID", ActorID).
Msgf("Exclusion key \"%s\" matched, but values did not match", key)
} else {
logger.Debug().
Str("ActorID", ActorID).
Msgf("Exclusion key \"%s\" did not match", key)

}
}
logger.Debug().
Str("ActorID", ActorID).
Msg("Exclusion settings didn't match, not excluding event")
return false
}

func processEvent(event *events.Message) {
// the Docker Events endpoint will return a struct events.Message
// https://pkg.go.dev/github.com/docker/docker/api/types/events#Message
Expand All @@ -373,12 +472,12 @@ func processEvent(event *events.Message) {
// Finishing this function not before a certain time before draining the next event from the event channel in main() solves the issue
timer := time.NewTimer(glb_arguments.Delay)

// if logging level is Debug, log the event
logger.Debug().Msgf("%#v", event)

//some events don't return Actor.ID, Actor.Attributes["image"] or Actor.Attributes["name"]
if len(event.Actor.ID) > 0 && strings.HasPrefix(event.Actor.ID, "sha256:") {
ActorID = strings.TrimPrefix(event.Actor.ID, "sha256:")[:8] //remove prefix + limit ActorID legth
if len(event.Actor.ID) > 0 {
if strings.HasPrefix(event.Actor.ID, "sha256:") {
ActorID = strings.TrimPrefix(event.Actor.ID, "sha256:")[:8] //remove prefix + limit ActorID legth
} else {
ActorID = event.Actor.ID[:8] //limit ActorID legth
}
}
if len(event.Actor.Attributes["image"]) > 0 {
ActorImage = event.Actor.Attributes["image"]
Expand Down Expand Up @@ -461,6 +560,7 @@ func processEvent(event *events.Message) {
func parseArgs() {
parser := arg.MustParse(&glb_arguments)

// Parse (include) filters
glb_arguments.Filter = make(map[string][]string)

for _, filter := range glb_arguments.FilterStrings {
Expand All @@ -473,6 +573,20 @@ func parseArgs() {
glb_arguments.Filter[key] = append(glb_arguments.Filter[key], val)
}

// Parse exclude filters
glb_arguments.Exclude = make(map[string][]string)

for _, exclude := range glb_arguments.ExcludeStrings {
pos := strings.Index(exclude, "=")
if pos == -1 {
parser.Fail("each filter should be of the form key=value")
}
//trim whitespaces and make first letter uppercase for key (to match events.Message key format)
key := cases.Title(language.English, cases.Compact).String(strings.TrimSpace(exclude[:pos]))
val := exclude[pos+1:]
glb_arguments.Exclude[key] = append(glb_arguments.Exclude[key], val)
}

}

func configureLogger(LogLevel string) {
Expand Down
5 changes: 3 additions & 2 deletions src/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ go 1.21

require (
github.com/alexflint/go-arg v1.4.3
github.com/docker/docker v25.0.2+incompatible
github.com/docker/docker v25.0.3+incompatible
github.com/gregdel/pushover v1.3.0
github.com/rs/zerolog v1.31.0
github.com/oleiade/reflections v1.0.1
github.com/rs/zerolog v1.32.0
golang.org/x/text v0.14.0
)

Expand Down
11 changes: 7 additions & 4 deletions src/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,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/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/docker v25.0.2+incompatible h1:/OaKeauroa10K4Nqavw4zlhcDq/WBcPMc5DbjOGgozY=
github.com/docker/docker v25.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v25.0.3+incompatible h1:D5fy/lYmY7bvZa0XTZ5/UJPljor41F+vdyJG5luQLfQ=
github.com/docker/docker v25.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
Expand Down Expand Up @@ -53,6 +53,8 @@ github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/oleiade/reflections v1.0.1 h1:D1XO3LVEYroYskEsoSiGItp9RUxG6jWnCVvrqH0HHQM=
github.com/oleiade/reflections v1.0.1/go.mod h1:rdFxbxq4QXVZWj0F+e9jqjDkc7dbp97vkRixKo2JR60=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
Expand All @@ -62,12 +64,13 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
Expand Down
25 changes: 23 additions & 2 deletions src/makefile
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
BINARY_NAME := docker-event-monitor
BINARY_NAME = docker-event-monitor

# using the ?= assignment operator: Assign only if variable is not set (e.g. via environment) yet
# this allows overwriting via CI
GIT_COMMIT ?= $(shell git --no-pager describe --always --abbrev=8 --dirty)
GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
GIT_VERSION ?= $(shell git --no-pager describe --tags --always --abbrev=8 --dirty)
GIT_DATE ?= $(shell git --no-pager show --date=short --format=%at --name-only | head -n 1)

# GIT_TAG is only set when a CI build is trigged via release tag
ifdef GIT_TAG
override GIT_VERSION = ${GIT_TAG}
endif
DATE := $(shell date +%s)

# in case 'git' or the repo is not available, GIT_XXX is set empty via the assignment above
# so we set them explicitly
ifeq ($(GIT_DATE),)
GIT_DATE = 0
endif
ifeq ($(GIT_COMMIT),)
GIT_COMMIT = "n/a"
endif
ifeq ($(GIT_BRANCH),)
GIT_BRANCH = "n/a"
endif
ifeq ($(GIT_VERSION),)
GIT_VERSION = "n/a"
endif

DATE = $(shell date +%s)
build:
CGO_ENABLED=0 go build -ldflags "-s -w -X 'main.version=${GIT_VERSION}' -X 'main.gitdate=${GIT_DATE}' -X 'main.date=${DATE}' -X 'main.commit=${GIT_COMMIT}' -X 'main.branch=${GIT_BRANCH}'" -o=${BINARY_NAME} docker-event-monitor.go

0 comments on commit 9a163f1

Please sign in to comment.