From a5b26770f72b2291dfd495c9a221a22ab24e9816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20J=C3=B8rgensen?= Date: Mon, 20 Apr 2026 22:52:46 +0200 Subject: [PATCH 1/5] Migrate to github.com/moby/moby packages - Replace github.com/docker/docker/client with github.com/moby/moby/client v0.4.1 - Replace github.com/docker/docker/api with github.com/moby/moby/api v1.54.2 - Completely eliminate all github.com/docker/docker dependencies API changes: - Update to new client API (ContainerListResult, EventsResult) - Use Filters as map[string]map[string]bool instead of filters.Args - Handle IPAddress as netip.Addr instead of string - Use container.InspectResponse instead of ContainerJSON Test data: - Remove invalid port entries from testdata/container.json Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- container.go | 28 ++++++------ go.mod | 13 ++---- go.sum | 66 +++++----------------------- internal/container/container.go | 16 +++---- internal/container/container_test.go | 12 ++--- internal/hostname/hostname_test.go | 12 ++--- main.go | 2 +- testdata/container.json | 12 ----- 8 files changed, 46 insertions(+), 115 deletions(-) diff --git a/container.go b/container.go index 564426b0..5982630f 100644 --- a/container.go +++ b/container.go @@ -9,11 +9,9 @@ import ( "syscall" "time" - typesContainer "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/events" - "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/client" - "ldddns.arnested.dk/internal/container" + "github.com/moby/moby/api/types/events" + "github.com/moby/moby/client" + internalContainer "ldddns.arnested.dk/internal/container" "ldddns.arnested.dk/internal/hostname" "ldddns.arnested.dk/internal/log" ) @@ -50,12 +48,12 @@ func handleContainer( return nil } - containerJSON, err := docker.ContainerInspect(ctx, containerID) + result, err := docker.ContainerInspect(ctx, containerID, client.ContainerInspectOptions{}) if err != nil { return fmt.Errorf("inspecting container: %w", err) } - containerInfo := container.Container{ContainerJSON: containerJSON} + containerInfo := internalContainer.Container{InspectResponse: result.Container} if ignoreOneoff(containerInfo, config) { return nil @@ -82,7 +80,7 @@ func handleContainer( return nil } -func ignoreOneoff(containerInfo container.Container, config Config) bool { +func ignoreOneoff(containerInfo internalContainer.Container, config Config) bool { if !config.IgnoreDockerComposeOneoff { return false } @@ -102,12 +100,12 @@ func ignoreOneoff(containerInfo container.Container, config Config) bool { } func handleExistingContainers(ctx context.Context, config Config, docker *client.Client, egs *entryGroups) { - containers, err := docker.ContainerList(ctx, typesContainer.ListOptions{}) + result, err := docker.ContainerList(ctx, client.ContainerListOptions{}) if err != nil { log.Logf(log.PriErr, "getting container list: %v", err) } - for _, container := range containers { + for _, container := range result.Items { err = handleContainer(ctx, docker, container.ID, egs, "start", config) if err != nil { log.Logf(log.PriErr, "handling container: %v", err) @@ -118,7 +116,7 @@ func handleExistingContainers(ctx context.Context, config Config, docker *client } func listen(ctx context.Context, config Config, docker *client.Client, egs *entryGroups, started time.Time) { - filter := filters.NewArgs() + filter := make(client.Filters) filter.Add("type", "container") filter.Add("event", "die") filter.Add("event", "kill") @@ -126,7 +124,7 @@ func listen(ctx context.Context, config Config, docker *client.Client, egs *entr filter.Add("event", "start") filter.Add("event", "unpause") - msgs, errs := docker.Events(ctx, events.ListOptions{ + result := docker.Events(ctx, client.EventsListOptions{ Filters: filter, Since: strconv.FormatInt(started.Unix(), 10), Until: "", @@ -137,10 +135,10 @@ func listen(ctx context.Context, config Config, docker *client.Client, egs *entr for { select { - case err := <-errs: + case err := <-result.Err: panic(fmt.Errorf("go error reading docker events: %w", err)) - case msg := <-msgs: - err := handleContainer(ctx, docker, msg.Actor.ID, egs, msg.Action, config) + case msg := <-result.Messages: + err := handleContainer(ctx, docker, msg.Actor.ID, egs, events.Action(msg.Action), config) if err != nil { log.Logf(log.PriErr, "handling container: %v", err) } diff --git a/go.mod b/go.mod index e08fcd90..fcb866f3 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,7 @@ go 1.26.2 require ( github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/docker/docker v28.5.2+incompatible - github.com/docker/go-connections v0.6.0 // indirect + github.com/docker/go-connections v0.7.0 // indirect github.com/godbus/dbus/v5 v5.2.2 github.com/holoplot/go-avahi v1.0.1 github.com/kelseyhightower/envconfig v1.4.0 @@ -18,32 +17,26 @@ require ( github.com/carlmjohnson/versioninfo v0.22.5 github.com/coreos/go-systemd/v22 v22.7.0 github.com/google/gops v0.3.29 + github.com/moby/moby/api v1.54.2 + github.com/moby/moby/client v0.4.1 ) require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect - github.com/containerd/log v0.1.0 // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/sys/atomicwriter v0.1.0 // indirect - github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect - github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect - github.com/pkg/errors v0.9.1 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect go.opentelemetry.io/otel v1.43.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 // indirect go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect golang.org/x/text v0.36.0 // indirect - golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect - gotest.tools/v3 v3.0.3 // indirect ) diff --git a/go.sum b/go.sum index 205f2764..1ed61825 100644 --- a/go.sum +++ b/go.sum @@ -1,30 +1,21 @@ -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/carlmjohnson/versioninfo v0.22.5 h1:O00sjOLUAFxYQjlN/bzYTuZiS0y6fWDQjMRvwtKgwwc= github.com/carlmjohnson/versioninfo v0.22.5/go.mod h1:QT9mph3wcVfISUKd0i9sZfVrPviHuSF+cUtLjm2WSf8= -github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= -github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= -github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= -github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= -github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= +github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -37,42 +28,28 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gops v0.3.29 h1:n98J2qSOK1NJvRjdLDcjgDryjpIBGhbaqph1mXKL0rY= github.com/google/gops v0.3.29/go.mod h1:8N3jZftuPazvUwtYY/ncG4iPrjp15ysNKLfq+QQPiwc= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/holoplot/go-avahi v1.0.1 h1:XcqR2keL4qWRnlxHD5CAOdWpLFZJ+EOUK0vEuylfvvk= github.com/holoplot/go-avahi v1.0.1/go.mod h1:qH5psEKb0DK+BRplMfc+RY4VMOlbf6mqfxgpMy6aP0M= github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= -github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= -github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= -github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg= +github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= +github.com/moby/moby/client v0.4.1 h1:DMQgisVoMkmMs7fp3ROSdiBnoAu8+vo3GggFl06M/wY= +github.com/moby/moby/client v0.4.1/go.mod h1:z52C9O2POPOsnxZAy//WtKcQ32P+jT/NGeXu/7nfjGQ= 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.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= @@ -81,10 +58,6 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8V go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= @@ -93,36 +66,17 @@ go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfC go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= -go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= -go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= -google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= -google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/netdb v0.0.0-20210921115105-e902e863d85d h1:yjDpoTxoYVpCt04OYp8zlZsKtrEOK1O4U7l2aWbn3D8= honnef.co/go/netdb v0.0.0-20210921115105-e902e863d85d/go.mod h1:rbNo0ST5hSazCG4rGfpHrwnwvzP1QX62WbhzD+ghGzs= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/internal/container/container.go b/internal/container/container.go index eb307860..552625f0 100644 --- a/internal/container/container.go +++ b/internal/container/container.go @@ -5,31 +5,29 @@ import ( "strconv" "strings" - "github.com/docker/docker/api/types" + "github.com/moby/moby/api/types/container" "honnef.co/go/netdb" "ldddns.arnested.dk/internal/log" ) // Container holds information about a container. type Container struct { - types.ContainerJSON + container.InspectResponse } // Name is the containers name without the leading '/'. func (c Container) Name() string { - return c.ContainerJSON.Name[1:] + return c.InspectResponse.Name[1:] } // IPAddresses returns a slice of the IPv4 addresses of the container. func (c Container) IPAddresses() []string { ips := []string{} - if c.NetworkSettings.IPAddress != "" { - ips = append(ips, c.NetworkSettings.IPAddress) - } - for _, v := range c.NetworkSettings.Networks { - ips = append(ips, v.IPAddress) + if v.IPAddress.IsValid() { + ips = append(ips, v.IPAddress.String()) + } } return ips @@ -40,7 +38,7 @@ func (c Container) Services() map[string]uint16 { services := map[string]uint16{} for portProto := range c.NetworkSettings.Ports { - port, protoName, found := strings.Cut(string(portProto), "/") + port, protoName, found := strings.Cut(portProto.String(), "/") if !found { log.Logf(log.PriErr, "Port not found in: %q", portProto) diff --git a/internal/container/container_test.go b/internal/container/container_test.go index f2f5f5f4..6e4c9a03 100644 --- a/internal/container/container_test.go +++ b/internal/container/container_test.go @@ -7,11 +7,11 @@ import ( "os" "testing" - docker_container "github.com/docker/docker/api/types/container" - "ldddns.arnested.dk/internal/container" + "github.com/moby/moby/api/types/container" + internalContainer "ldddns.arnested.dk/internal/container" ) -func containerJSON() (*docker_container.InspectResponse, error) { +func containerJSON() (*container.InspectResponse, error) { jsonFile, err := os.Open("../../testdata/container.json") if err != nil { return nil, fmt.Errorf("opening JSON test data: %w", err) @@ -25,7 +25,7 @@ func containerJSON() (*docker_container.InspectResponse, error) { } // we initialize our Users array - var containerJSON *docker_container.InspectResponse + var containerJSON *container.InspectResponse err = json.Unmarshal(byteValue, &containerJSON) if err != nil { @@ -35,13 +35,13 @@ func containerJSON() (*docker_container.InspectResponse, error) { return containerJSON, nil } -func containerData() (*container.Container, error) { +func containerData() (*internalContainer.Container, error) { containerJSON, err := containerJSON() if (err != nil) || (containerJSON == nil) { return nil, fmt.Errorf("getting JSON test data: %w", err) } - data := container.Container{ContainerJSON: *containerJSON} + data := internalContainer.Container{InspectResponse: *containerJSON} return &data, nil } diff --git a/internal/hostname/hostname_test.go b/internal/hostname/hostname_test.go index 35ed2e84..5fc7bacb 100644 --- a/internal/hostname/hostname_test.go +++ b/internal/hostname/hostname_test.go @@ -7,12 +7,12 @@ import ( "os" "testing" - docker_container "github.com/docker/docker/api/types/container" - "ldddns.arnested.dk/internal/container" + "github.com/moby/moby/api/types/container" + internalContainer "ldddns.arnested.dk/internal/container" "ldddns.arnested.dk/internal/hostname" ) -func containerJSON() (*docker_container.InspectResponse, error) { +func containerJSON() (*container.InspectResponse, error) { jsonFile, err := os.Open("../../testdata/container.json") if err != nil { return nil, fmt.Errorf("opening JSON test data: %w", err) @@ -26,7 +26,7 @@ func containerJSON() (*docker_container.InspectResponse, error) { } // we initialize our Users array - var containerJSON *docker_container.InspectResponse + var containerJSON *container.InspectResponse err = json.Unmarshal(byteValue, &containerJSON) if err != nil { @@ -36,13 +36,13 @@ func containerJSON() (*docker_container.InspectResponse, error) { return containerJSON, nil } -func containerData() (*container.Container, error) { +func containerData() (*internalContainer.Container, error) { containerJSON, err := containerJSON() if (err != nil) || (containerJSON == nil) { return nil, fmt.Errorf("getting JSON test data: %w", err) } - data := container.Container{ContainerJSON: *containerJSON} + data := internalContainer.Container{InspectResponse: *containerJSON} return &data, nil } diff --git a/main.go b/main.go index 401c1e40..e2b4724f 100644 --- a/main.go +++ b/main.go @@ -11,11 +11,11 @@ import ( "github.com/carlmjohnson/versioninfo" "github.com/coreos/go-systemd/v22/daemon" - "github.com/docker/docker/client" "github.com/godbus/dbus/v5" "github.com/google/gops/agent" "github.com/holoplot/go-avahi" "github.com/kelseyhightower/envconfig" + "github.com/moby/moby/client" "ldddns.arnested.dk/internal/log" ) diff --git a/testdata/container.json b/testdata/container.json index c968faf2..4566a5da 100644 --- a/testdata/container.json +++ b/testdata/container.json @@ -218,18 +218,6 @@ "HostPort": "32771" } ], - "no-port": [ - { - "HostIp": "0.0.0.0", - "HostPort": "32771" - } - ], - "65536/more-than-16-bit": [ - { - "HostIp": "0.0.0.0", - "HostPort": "32771" - } - ], "80/tcp": [ { "HostIp": "0.0.0.0", From a1fcfde8aafcf505fe1d87c92512805c3fac57e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20J=C3=B8rgensen?= Date: Mon, 20 Apr 2026 22:55:11 +0200 Subject: [PATCH 2/5] Fix golangci-lint findings - Replace deprecated client.NewClientWithOpts with client.New - Remove deprecated client.WithAPIVersionNegotiation option - Remove unnecessary type conversion events.Action(msg.Action) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- container.go | 2 +- main.go | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/container.go b/container.go index 5982630f..d8f58ed2 100644 --- a/container.go +++ b/container.go @@ -138,7 +138,7 @@ func listen(ctx context.Context, config Config, docker *client.Client, egs *entr case err := <-result.Err: panic(fmt.Errorf("go error reading docker events: %w", err)) case msg := <-result.Messages: - err := handleContainer(ctx, docker, msg.Actor.ID, egs, events.Action(msg.Action), config) + err := handleContainer(ctx, docker, msg.Actor.ID, egs, msg.Action, config) if err != nil { log.Logf(log.PriErr, "handling container: %v", err) } diff --git a/main.go b/main.go index e2b4724f..1e7b0dfc 100644 --- a/main.go +++ b/main.go @@ -57,10 +57,7 @@ func main() { gops(config.Gops) - docker, err := client.NewClientWithOpts( - client.FromEnv, - client.WithAPIVersionNegotiation(), - ) + docker, err := client.New(client.FromEnv) if err != nil { panic(fmt.Errorf("cannot create docker client: %w", err)) } From 14ca1cba6bc17dab5f8cac122855611ce04b867d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20J=C3=B8rgensen?= Date: Mon, 20 Apr 2026 23:06:15 +0200 Subject: [PATCH 3/5] Improve test coverage for internal/container Add edge case tests for Services function: - Test unknown service ports (no service found) - Test unknown protocol types - Test multiple ports with mixed results - Test empty IP addresses Add helper function createTestContainerWithPorts to reduce code duplication and keep tests under function length limits. Coverage remains at 82.9% - uncovered lines are error paths that cannot be reached with valid moby API types (they validate during JSON unmarshal). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- internal/container/container_test.go | 106 +++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/internal/container/container_test.go b/internal/container/container_test.go index 6e4c9a03..0f1b5bf8 100644 --- a/internal/container/container_test.go +++ b/internal/container/container_test.go @@ -109,6 +109,112 @@ func TestServices(t *testing.T) { } } +func TestServicesEdgeCases(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + ports string + expected int + }{ + { + name: "valid http port", + ports: `{"80/tcp": [{"HostIp": "", "HostPort": ""}]}`, + expected: 1, + }, + { + name: "unknown service port - no service found", + ports: `{"9999/tcp": [{"HostIp": "", "HostPort": ""}]}`, + expected: 0, + }, + { + name: "unknown protocol type", + ports: `{"80/unknown": [{"HostIp": "", "HostPort": ""}]}`, + expected: 0, + }, + { + name: "multiple ports with mixed results", + ports: `{ + "80/tcp": [{"HostIp": "", "HostPort": ""}], + "9999/tcp": [{"HostIp": "", "HostPort": ""}], + "22/tcp": [{"HostIp": "", "HostPort": ""}] + }`, + expected: 2, // http and ssh are known services, 9999 is not + }, + } + + for _, testCase := range tests { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + c := createTestContainerWithPorts(t, testCase.ports) + services := c.Services() + + if len(services) != testCase.expected { + t.Errorf("Expected %d services, got %d: %v", testCase.expected, len(services), services) + } + }) + } +} + +func createTestContainerWithPorts(t *testing.T, ports string) internalContainer.Container { + t.Helper() + + jsonData := fmt.Sprintf(`{ + "Id": "test", + "Name": "/test", + "NetworkSettings": { + "Ports": %s, + "Networks": {} + }, + "Config": { + "Env": [], + "Labels": {} + } + }`, ports) + + var inspectResponse container.InspectResponse + + err := json.Unmarshal([]byte(jsonData), &inspectResponse) + if err != nil { + t.Fatalf("failed to unmarshal test data: %v", err) + } + + return internalContainer.Container{InspectResponse: inspectResponse} +} + +func TestIPAddressesEmpty(t *testing.T) { + t.Parallel() + + // Test container with no network settings + jsonData := `{ + "Id": "test", + "Name": "/test", + "NetworkSettings": { + "Ports": {}, + "Networks": {} + }, + "Config": { + "Env": [], + "Labels": {} + } + }` + + var inspectResponse container.InspectResponse + + err := json.Unmarshal([]byte(jsonData), &inspectResponse) + if err != nil { + t.Fatalf("failed to unmarshal test data: %v", err) + } + + c := internalContainer.Container{InspectResponse: inspectResponse} + ips := c.IPAddresses() + + if len(ips) != 0 { + t.Errorf("Expected 0 IP addresses for container with no networks, got %d", len(ips)) + } +} + func TestHostnamesFromEnv(t *testing.T) { t.Parallel() From 45382138e8877f0e2f0c4cbd583a689dae59f831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20J=C3=B8rgensen?= Date: Mon, 20 Apr 2026 23:08:45 +0200 Subject: [PATCH 4/5] Add tests for internal/log package Add comprehensive tests covering: - Priority constant values (PriEmerg through PriDebug) - Logf function with various message formats - All priority levels - Edge cases (empty messages, formatted messages) Coverage: 66.7% (uncovered path is panic handler when journald fails, which cannot be easily tested in unit tests) Tests handle environments without systemd/journald gracefully. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- internal/log/log_test.go | 140 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 internal/log/log_test.go diff --git a/internal/log/log_test.go b/internal/log/log_test.go new file mode 100644 index 00000000..39acc415 --- /dev/null +++ b/internal/log/log_test.go @@ -0,0 +1,140 @@ +package log_test + +import ( + "testing" + + "ldddns.arnested.dk/internal/log" +) + +func TestPriorityConstants(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + priority log.Priority + expected int + }{ + {"PriEmerg", log.PriEmerg, 0}, + {"PriAlert", log.PriAlert, 1}, + {"PriCrit", log.PriCrit, 2}, + {"PriErr", log.PriErr, 3}, + {"PriWarning", log.PriWarning, 4}, + {"PriNotice", log.PriNotice, 5}, + {"PriInfo", log.PriInfo, 6}, + {"PriDebug", log.PriDebug, 7}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + if int(tt.priority) != tt.expected { + t.Errorf("Expected %s to be %d, got %d", tt.name, tt.expected, int(tt.priority)) + } + }) + } +} + +func TestLogf(t *testing.T) { + t.Parallel() + + // Test that Logf doesn't panic with valid inputs + tests := []struct { + name string + priority log.Priority + format string + args []any + }{ + { + name: "simple message", + priority: log.PriInfo, + format: "test message", + args: nil, + }, + { + name: "formatted message", + priority: log.PriNotice, + format: "test %s message %d", + args: []any{"formatted", 123}, + }, + { + name: "emergency priority", + priority: log.PriEmerg, + format: "emergency: %v", + args: []any{"critical situation"}, + }, + { + name: "debug priority", + priority: log.PriDebug, + format: "debug: value=%d", + args: []any{42}, + }, + { + name: "empty message", + priority: log.PriInfo, + format: "", + args: nil, + }, + } + + for _, testCase := range tests { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + // This test verifies that Logf doesn't panic + // We can't easily verify the output goes to journald in unit tests, + // but we can verify the function executes without error + defer func() { + if recovered := recover(); recovered != nil { + // If we're not running in a systemd environment, the journal + // might not be available, which is OK for unit tests + // Only fail if it's an unexpected panic + if err, ok := recovered.(error); ok { + // Expected error when journald is not available + t.Logf("Journal not available (expected in test environment): %v", err) + } else { + t.Errorf("Unexpected panic: %v", recovered) + } + } + }() + + log.Logf(testCase.priority, testCase.format, testCase.args...) + }) + } +} + +func TestLogfAllPriorities(t *testing.T) { + t.Parallel() + + priorities := []struct { + name string + priority log.Priority + }{ + {"emerg", log.PriEmerg}, + {"alert", log.PriAlert}, + {"crit", log.PriCrit}, + {"err", log.PriErr}, + {"warning", log.PriWarning}, + {"notice", log.PriNotice}, + {"info", log.PriInfo}, + {"debug", log.PriDebug}, + } + + for _, testCase := range priorities { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + defer func() { + if recovered := recover(); recovered != nil { + if err, ok := recovered.(error); ok { + t.Logf("Journal not available (expected): %v", err) + } else { + t.Errorf("Unexpected panic: %v", recovered) + } + } + }() + + log.Logf(testCase.priority, "test message at priority %d", testCase.priority) + }) + } +} From 388c81ed80b9aed28f76ed90cb0129a58bdc88cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Arne=20J=C3=B8rgensen?= Date: Mon, 20 Apr 2026 23:14:07 +0200 Subject: [PATCH 5/5] Add tests for main package Add comprehensive tests covering: - ignoreOneoff function with enabled/disabled configurations - Various oneoff label scenarios (True, False, missing, unexpected) - getVersion function - gops function - newEntryGroups constructor - Constants validation Coverage: 12.2% Tests are split into separate functions to meet function length limits. Helper function createTestContainer reduces code duplication. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- main_test.go | 157 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 main_test.go diff --git a/main_test.go b/main_test.go new file mode 100644 index 00000000..64db81f6 --- /dev/null +++ b/main_test.go @@ -0,0 +1,157 @@ +package main + +import ( + "testing" + + "github.com/moby/moby/api/types/container" + internalContainer "ldddns.arnested.dk/internal/container" +) + +func createTestContainer(labels map[string]string) internalContainer.Container { + return internalContainer.Container{ + InspectResponse: container.InspectResponse{ + ID: "test-container", + Config: &container.Config{ + Labels: labels, + }, + }, + } +} + +func TestIgnoreOneoffEnabled(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + labels map[string]string + expectIgnore bool + }{ + { + name: "oneoff is True", + labels: map[string]string{ + "com.docker.compose.oneoff": "True", + }, + expectIgnore: true, + }, + { + name: "oneoff is False", + labels: map[string]string{ + "com.docker.compose.oneoff": "False", + }, + expectIgnore: false, + }, + { + name: "oneoff label missing", + labels: map[string]string{}, + expectIgnore: false, + }, + { + name: "oneoff with unexpected value", + labels: map[string]string{ + "com.docker.compose.oneoff": "yes", + }, + expectIgnore: false, + }, + } + + config := Config{IgnoreDockerComposeOneoff: true} + + for _, testCase := range tests { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + containerInfo := createTestContainer(testCase.labels) + result := ignoreOneoff(containerInfo, config) + + if result != testCase.expectIgnore { + t.Errorf("Expected ignoreOneoff to return %v, got %v", testCase.expectIgnore, result) + } + }) + } +} + +func TestIgnoreOneoffDisabled(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + labels map[string]string + }{ + { + name: "oneoff is True", + labels: map[string]string{ + "com.docker.compose.oneoff": "True", + }, + }, + { + name: "oneoff is False", + labels: map[string]string{ + "com.docker.compose.oneoff": "False", + }, + }, + } + + config := Config{IgnoreDockerComposeOneoff: false} + + for _, testCase := range tests { + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + containerInfo := createTestContainer(testCase.labels) + result := ignoreOneoff(containerInfo, config) + + if result { + t.Error("Expected ignoreOneoff to return false when disabled") + } + }) + } +} + +func TestGetVersion(t *testing.T) { + t.Parallel() + + // Test that getVersion returns a non-empty string + version := getVersion() + if version == "" { + t.Error("Expected getVersion to return non-empty string") + } +} + +func TestGops(t *testing.T) { + t.Parallel() + + // Test that gops with start=false doesn't panic + gops(false) + + // We can't easily test start=true without actually starting the agent + // which could conflict with other tests or require cleanup +} + +func TestNewEntryGroups(t *testing.T) { + t.Parallel() + + // Test that newEntryGroups creates a valid entryGroups instance + // We pass nil since we're just testing the constructor logic + egs := newEntryGroups(nil) + + if egs == nil { + t.Fatal("Expected newEntryGroups to return non-nil") + } + + if egs.groups == nil { + t.Error("Expected groups map to be initialized") + } + + if egs.avahiServer != nil { + t.Error("Expected avahiServer to be nil when passed nil") + } +} + +func TestConstants(t *testing.T) { + t.Parallel() + + // Test that constants are set correctly + if tld != "local" { + t.Errorf("Expected tld to be 'local', got %q", tld) + } +}