767cc89e6c
github.com/docker/docker is frozen at v28.5.2 — moby publishes v29+ under
github.com/moby/moby/v2, with the API client split out into the standalone
github.com/moby/moby/client and github.com/moby/moby/api modules. That leaves
five Dependabot alerts on the old module path with no reachable fix version:
CVE-2026-42306 high docker cp bind mount redirection <= 28.5.2
CVE-2026-41567 high PUT /containers/{id}/archive <= 28.5.2
CVE-2026-41568 medium docker cp symlink swap <= 28.5.2
CVE-2026-34040 high AuthZ plugin bypass < 29.3.1
CVE-2026-33997 medium plugin privilege off-by-one < 29.3.1
All five are daemon-side defects, and we only ever imported the client. Moving
to the split client module drops the daemon source out of the module graph
entirely, so these alerts resolve and future daemon advisories will not flag us
at all. It also trims six transitive dependencies.
The new client takes an options struct per call and returns a result struct, so
this is a mechanical rewrite of the wrapper. Behavioural notes:
- Ports moved from docker/go-connections/nat to moby/api/types/network, where
Port is opaque and PortBinding.HostIP is a netip.Addr. buildDockerPortBindings
now returns an error for out-of-range ports and unparseable host IPs instead
of handing the daemon a malformed request. An empty HostIP still maps to the
zero address, preserving "publish on all interfaces".
- DiskUsage needs Verbose to get per-image and per-container records; the
aggregate LayersSize is now Images.TotalSize.
- BuildCachePrune no longer takes KeepStorage — the client derives the legacy
keep-storage parameter from ReservedSpace for daemons on API <= 1.47.
- ImagesPrune is now ImagePrune.
- Container state and health are typed string enums, and InspectResponse.Config
and .State are pointers, so inspect mapping guards for nil.
Also corrects the RUSTSEC-2024-0429 note in the cargo-audit ignore list. It
claimed the advisory resolves once Tauri moves to webkit2gtk-4.1, but Tauri v2
is already on those bindings and still resolves glib to 0.18.5; the real
blocker is that gtk3-rs is archived. Backport submitted upstream.
Co-authored-by: Kandev Agent <agent@kandev.dev>
133 lines
4.0 KiB
Go
133 lines
4.0 KiB
Go
package docker
|
|
|
|
import (
|
|
"fmt"
|
|
"net/netip"
|
|
"testing"
|
|
|
|
"github.com/moby/moby/api/types/network"
|
|
)
|
|
|
|
func TestNormalizeDockerHostIP(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
in netip.Addr
|
|
want string
|
|
}{
|
|
{name: "unset", in: netip.Addr{}, want: "127.0.0.1"},
|
|
{name: "ipv4 wildcard", in: netip.MustParseAddr("0.0.0.0"), want: "127.0.0.1"},
|
|
{name: "ipv6 wildcard", in: netip.MustParseAddr("::"), want: "127.0.0.1"},
|
|
{name: "ipv4 loopback", in: netip.MustParseAddr("127.0.0.1"), want: "127.0.0.1"},
|
|
{name: "ipv4 host", in: netip.MustParseAddr("10.0.0.5"), want: "10.0.0.5"},
|
|
{name: "ipv6 loopback", in: netip.MustParseAddr("::1"), want: "::1"},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := normalizeDockerHostIP(tc.in); got != tc.want {
|
|
t.Errorf("normalizeDockerHostIP(%v) = %q, want %q", tc.in, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuildDockerPortBindings_EmptyReturnsNil(t *testing.T) {
|
|
exposed, bindings, err := buildDockerPortBindings(nil)
|
|
if err != nil {
|
|
t.Fatalf("buildDockerPortBindings: %v", err)
|
|
}
|
|
if exposed != nil {
|
|
t.Errorf("expected nil exposed ports, got %v", exposed)
|
|
}
|
|
if bindings != nil {
|
|
t.Errorf("expected nil bindings, got %v", bindings)
|
|
}
|
|
}
|
|
|
|
func TestBuildDockerPortBindings_AssignsContainerAndHost(t *testing.T) {
|
|
in := []PortBindingConfig{
|
|
{ContainerPort: 8080, HostIP: "127.0.0.1", HostPort: "0"},
|
|
{ContainerPort: 9000, HostIP: "0.0.0.0", HostPort: "9001"},
|
|
}
|
|
exposed, bindings, err := buildDockerPortBindings(in)
|
|
if err != nil {
|
|
t.Fatalf("buildDockerPortBindings: %v", err)
|
|
}
|
|
|
|
if got := len(exposed); got != 2 {
|
|
t.Fatalf("exposed ports = %d, want 2", got)
|
|
}
|
|
for _, b := range in {
|
|
key := network.MustParsePort(fmt.Sprintf("%d/tcp", b.ContainerPort))
|
|
if _, ok := exposed[key]; !ok {
|
|
t.Errorf("exposed missing %s", key)
|
|
}
|
|
got := bindings[key]
|
|
if len(got) != 1 {
|
|
t.Fatalf("bindings[%s] = %d entries, want 1", key, len(got))
|
|
}
|
|
if got[0].HostIP.String() != b.HostIP || got[0].HostPort != b.HostPort {
|
|
t.Errorf("bindings[%s] = %+v, want host_ip=%q host_port=%q", key, got[0], b.HostIP, b.HostPort)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildDockerPortBindings_DeduplicatesContainerPort(t *testing.T) {
|
|
in := []PortBindingConfig{
|
|
{ContainerPort: 7000, HostIP: "127.0.0.1", HostPort: "0"},
|
|
{ContainerPort: 7000, HostIP: "10.0.0.5", HostPort: "7000"},
|
|
}
|
|
_, bindings, err := buildDockerPortBindings(in)
|
|
if err != nil {
|
|
t.Fatalf("buildDockerPortBindings: %v", err)
|
|
}
|
|
got := bindings[network.MustParsePort("7000/tcp")]
|
|
if len(got) != 2 {
|
|
t.Fatalf("want both bindings on port 7000, got %d", len(got))
|
|
}
|
|
}
|
|
|
|
func TestBuildDockerPortBindings_RejectsInvalidInput(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
in PortBindingConfig
|
|
}{
|
|
{name: "container port zero", in: PortBindingConfig{ContainerPort: 0, HostPort: "1"}},
|
|
{name: "container port too large", in: PortBindingConfig{ContainerPort: 70000, HostPort: "1"}},
|
|
{name: "host ip not an address", in: PortBindingConfig{ContainerPort: 8080, HostIP: "localhost"}},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if _, _, err := buildDockerPortBindings([]PortBindingConfig{tc.in}); err == nil {
|
|
t.Fatal("expected an error, got nil")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuildDockerPortBindings_EmptyHostIPPublishesOnAllInterfaces(t *testing.T) {
|
|
_, bindings, err := buildDockerPortBindings([]PortBindingConfig{
|
|
{ContainerPort: 8080, HostIP: "", HostPort: "8080"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("buildDockerPortBindings: %v", err)
|
|
}
|
|
got := bindings[network.MustParsePort("8080/tcp")]
|
|
if len(got) != 1 {
|
|
t.Fatalf("bindings = %d entries, want 1", len(got))
|
|
}
|
|
if got[0].HostIP.IsValid() {
|
|
t.Errorf("host IP = %v, want the zero address (all interfaces)", got[0].HostIP)
|
|
}
|
|
}
|
|
|
|
func TestParseHostPort(t *testing.T) {
|
|
if got, err := parseHostPort("9001"); err != nil || got != 9001 {
|
|
t.Fatalf("parseHostPort(9001) = %d, %v", got, err)
|
|
}
|
|
for _, in := range []string{"", "abc", "70000", "-1"} {
|
|
if _, err := parseHostPort(in); err == nil {
|
|
t.Errorf("parseHostPort(%q) = nil error, want an error", in)
|
|
}
|
|
}
|
|
}
|