From c1fe0f3264c98032b91c23cff8d3125dbfe190cc Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Tue, 14 May 2024 10:14:03 +0200 Subject: [PATCH 1/3] feat: watch network connect / disconnect event --- internal/generator/generator.go | 17 +++++++++++++++-- internal/generator/generator_test.go | 10 +++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/internal/generator/generator.go b/internal/generator/generator.go index 327c99a9..790a22c9 100644 --- a/internal/generator/generator.go +++ b/internal/generator/generator.go @@ -6,6 +6,7 @@ import ( "os" "os/exec" "os/signal" + "slices" "strings" "sync" "syscall" @@ -281,8 +282,20 @@ func (g *generator) generateFromEvents() { time.Sleep(10 * time.Second) break } - if event.Status == "start" || event.Status == "stop" || event.Status == "die" || strings.Contains(event.Status, "health_status:") { - log.Printf("Received event %s for container %s", event.Status, event.ID[:12]) + + watchedEvent := false + + switch event.Type { + case "container": + watchedContainerActions := []string{"start", "stop", "die", "health_status"} + watchedEvent = slices.Contains(watchedContainerActions, event.Action) + case "network": + watchedNetworkActions := []string{"connect", "disconnect"} + watchedEvent = slices.Contains(watchedNetworkActions, event.Action) + } + + if watchedEvent { + log.Printf("Received event %s for %s %s", event.Action, event.Type, event.Actor.ID[:12]) // fanout event to all watchers for _, watcher := range watchers { watcher <- event diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go index d8f69fec..79d39a54 100644 --- a/internal/generator/generator_test.go +++ b/internal/generator/generator_test.go @@ -26,12 +26,12 @@ func TestGenerateFromEvents(t *testing.T) { var counter atomic.Int32 eventsResponse := ` -{"status":"start","id":"8dfafdbc3a40","from":"base:latest","time":1374067924} -{"status":"stop","id":"8dfafdbc3a40","from":"base:latest","time":1374067966} -{"status":"start","id":"8dfafdbc3a40","from":"base:latest","time":1374067970} -{"status":"destroy","id":"8dfafdbc3a40","from":"base:latest","time":1374067990}` +{"Type":"container","Action":"start","Actor": {"ID":"8dfafdbc3a40"},"Time":1374067924} +{"Type":"container","Action":"stop","Actor": {"ID":"8dfafdbc3a40"},"Time":1374067966} +{"Type":"container","Action":"start","Actor": {"ID":"8dfafdbc3a40"},"Time":1374067970} +{"Type":"container","Action":"destroy","Actor": {"ID":"8dfafdbc3a40"},"Time":1374067990}` infoResponse := `{"Containers":1,"Images":1,"Debug":false,"NFd":11,"NGoroutines":21,"MemoryLimit":true,"SwapLimit":false}` - versionResponse := `{"Version":"1.8.0","Os":"Linux","KernelVersion":"3.18.5-tinycore64","GoVersion":"go1.4.1","GitCommit":"a8a31ef","Arch":"amd64","ApiVersion":"1.19"}` + versionResponse := `{"Version":"19.03.12","Os":"Linux","KernelVersion":"4.19.76-linuxkit","GoVersion":"go1.13.14","GitCommit":"48a66213fe","Arch":"amd64","ApiVersion":"1.40"}` server, _ := dockertest.NewServer("127.0.0.1:0", nil, nil) server.CustomHandler("/events", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { From 64d297a17f6508fcb50aff7f6186d8da7542159c Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Sun, 25 May 2025 17:03:09 +0200 Subject: [PATCH 2/3] refactor: use go-dockerclient builtin event filtering --- internal/generator/generator.go | 31 +++++++++++----------------- internal/generator/generator_test.go | 3 +-- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/internal/generator/generator.go b/internal/generator/generator.go index 790a22c9..3672d928 100644 --- a/internal/generator/generator.go +++ b/internal/generator/generator.go @@ -6,7 +6,6 @@ import ( "os" "os/exec" "os/signal" - "slices" "strings" "sync" "syscall" @@ -250,7 +249,14 @@ func (g *generator) generateFromEvents() { break } if !watching { - err := client.AddEventListener(eventChan) + options := docker.EventsOptions{ + Filters: map[string][]string{ + "event": {"start", "stop", "die", "health_status", "connect", "disconnect"}, + "type": {"container", "network"}, + }, + } + + err := client.AddEventListenerWithOptions(options, eventChan) if err != nil && err != docker.ErrListenerAlreadyExists { log.Printf("Error registering docker event listener: %s", err) time.Sleep(10 * time.Second) @@ -283,23 +289,10 @@ func (g *generator) generateFromEvents() { break } - watchedEvent := false - - switch event.Type { - case "container": - watchedContainerActions := []string{"start", "stop", "die", "health_status"} - watchedEvent = slices.Contains(watchedContainerActions, event.Action) - case "network": - watchedNetworkActions := []string{"connect", "disconnect"} - watchedEvent = slices.Contains(watchedNetworkActions, event.Action) - } - - if watchedEvent { - log.Printf("Received event %s for %s %s", event.Action, event.Type, event.Actor.ID[:12]) - // fanout event to all watchers - for _, watcher := range watchers { - watcher <- event - } + log.Printf("Received event %s for %s %s", event.Action, event.Type, event.Actor.ID[:12]) + // fanout event to all watchers + for _, watcher := range watchers { + watcher <- event } case <-time.After(10 * time.Second): // check for docker liveness diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go index 79d39a54..bbf5c814 100644 --- a/internal/generator/generator_test.go +++ b/internal/generator/generator_test.go @@ -28,8 +28,7 @@ func TestGenerateFromEvents(t *testing.T) { eventsResponse := ` {"Type":"container","Action":"start","Actor": {"ID":"8dfafdbc3a40"},"Time":1374067924} {"Type":"container","Action":"stop","Actor": {"ID":"8dfafdbc3a40"},"Time":1374067966} -{"Type":"container","Action":"start","Actor": {"ID":"8dfafdbc3a40"},"Time":1374067970} -{"Type":"container","Action":"destroy","Actor": {"ID":"8dfafdbc3a40"},"Time":1374067990}` +{"Type":"container","Action":"start","Actor": {"ID":"8dfafdbc3a40"},"Time":1374067970}` infoResponse := `{"Containers":1,"Images":1,"Debug":false,"NFd":11,"NGoroutines":21,"MemoryLimit":true,"SwapLimit":false}` versionResponse := `{"Version":"19.03.12","Os":"Linux","KernelVersion":"4.19.76-linuxkit","GoVersion":"go1.13.14","GitCommit":"48a66213fe","Arch":"amd64","ApiVersion":"1.40"}` From 654bbcad27b5561af6133d8f6f3d983d1e5e5545 Mon Sep 17 00:00:00 2001 From: Nicolas Duchon Date: Sun, 25 May 2025 22:27:37 +0200 Subject: [PATCH 3/3] refactor: make event filter configurable --- README.md | 5 +++++ cmd/docker-gen/main.go | 19 ++++++++++++------- internal/generator/generator.go | 27 ++++++++++++++------------- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 74077ac2..14e2c926 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,11 @@ Options: config files with template directives. Config files will be merged if this option is specified multiple times. (default []) -endpoint string docker api endpoint (tcp|unix://..). Default unix:///var/run/docker.sock + -event-filter value + additional filter for event watched by docker-gen (e.g -event-filter event=connect -event-filter event=disconnect). + You can pass this option multiple times to combine filters. + By default docker-gen listen for container events start, stop, die and health_status. + https://docs.docker.com/engine/reference/commandline/events/#filtering-events -interval int notify command interval (secs) -keep-blank-lines diff --git a/cmd/docker-gen/main.go b/cmd/docker-gen/main.go index 8ee86364..1fc351a7 100644 --- a/cmd/docker-gen/main.go +++ b/cmd/docker-gen/main.go @@ -36,6 +36,7 @@ var ( includeStopped bool configFiles stringslice configs config.ConfigFile + eventFilter mapstringslice = mapstringslice{"event": {"start", "stop", "die", "health_status"}} interval int keepBlankLines bool endpoint string @@ -129,6 +130,9 @@ func initFlags() { flag.StringVar(&tlsCaCert, "tlscacert", filepath.Join(certPath, "ca.pem"), "path to TLS CA certificate file") flag.BoolVar(&tlsVerify, "tlsverify", os.Getenv("DOCKER_TLS_VERIFY") != "", "verify docker daemon's TLS certicate") + flag.Var(&eventFilter, "event-filter", + "additional filter for event watched by docker-gen (e.g -event-filter event=connect -event-filter event=disconnect). You can pass this option multiple times to combine filters. By default docker-gen listen for container events start, stop, die and health_status. https://docs.docker.com/engine/reference/commandline/events/#filtering-events") + flag.Usage = usage flag.Parse() } @@ -202,13 +206,14 @@ func main() { } generator, err := generator.NewGenerator(generator.GeneratorConfig{ - Endpoint: endpoint, - TLSKey: tlsKey, - TLSCert: tlsCert, - TLSCACert: tlsCaCert, - TLSVerify: tlsVerify, - All: all, - ConfigFile: configs, + Endpoint: endpoint, + TLSKey: tlsKey, + TLSCert: tlsCert, + TLSCACert: tlsCaCert, + TLSVerify: tlsVerify, + All: all, + EventFilter: eventFilter, + ConfigFile: configs, }) if err != nil { diff --git a/internal/generator/generator.go b/internal/generator/generator.go index 3672d928..e845fb01 100644 --- a/internal/generator/generator.go +++ b/internal/generator/generator.go @@ -26,6 +26,7 @@ type generator struct { TLSVerify bool TLSCert, TLSCaCert, TLSKey string All bool + EventFilter map[string][]string wg sync.WaitGroup retry bool @@ -40,6 +41,8 @@ type GeneratorConfig struct { TLSVerify bool All bool + EventFilter map[string][]string + ConfigFile config.ConfigFile } @@ -63,15 +66,16 @@ func NewGenerator(gc GeneratorConfig) (*generator, error) { context.SetDockerEnv(apiVersion) return &generator{ - Client: client, - Endpoint: gc.Endpoint, - TLSVerify: gc.TLSVerify, - TLSCert: gc.TLSCert, - TLSCaCert: gc.TLSCACert, - TLSKey: gc.TLSKey, - All: gc.All, - Configs: gc.ConfigFile, - retry: true, + Client: client, + Endpoint: gc.Endpoint, + TLSVerify: gc.TLSVerify, + TLSCert: gc.TLSCert, + TLSCaCert: gc.TLSCACert, + TLSKey: gc.TLSKey, + All: gc.All, + EventFilter: gc.EventFilter, + Configs: gc.ConfigFile, + retry: true, }, nil } @@ -250,10 +254,7 @@ func (g *generator) generateFromEvents() { } if !watching { options := docker.EventsOptions{ - Filters: map[string][]string{ - "event": {"start", "stop", "die", "health_status", "connect", "disconnect"}, - "type": {"container", "network"}, - }, + Filters: g.EventFilter, } err := client.AddEventListenerWithOptions(options, eventChan)