Browse code

internal/testutils: merge with internal/testutil

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2025/09/07 04:39:13
Showing 84 changed files
... ...
@@ -10,7 +10,7 @@ import (
10 10
 	"github.com/containerd/containerd/v2/pkg/namespaces"
11 11
 	"github.com/containerd/log/logtest"
12 12
 	"github.com/moby/moby/v2/daemon/server/backend"
13
-	"github.com/moby/moby/v2/internal/testutils/specialimage"
13
+	"github.com/moby/moby/v2/internal/testutil/specialimage"
14 14
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
15 15
 	"gotest.tools/v3/assert"
16 16
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -18,7 +18,7 @@ import (
18 18
 	imagetypes "github.com/moby/moby/api/types/image"
19 19
 	"github.com/moby/moby/v2/daemon/container"
20 20
 	"github.com/moby/moby/v2/daemon/server/imagebackend"
21
-	"github.com/moby/moby/v2/internal/testutils/specialimage"
21
+	"github.com/moby/moby/v2/internal/testutil/specialimage"
22 22
 	"github.com/opencontainers/go-digest"
23 23
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
24 24
 	"gotest.tools/v3/assert"
... ...
@@ -17,8 +17,8 @@ import (
17 17
 	"github.com/moby/go-archive"
18 18
 	"github.com/moby/moby/v2/daemon/server/backend"
19 19
 	"github.com/moby/moby/v2/daemon/server/imagebackend"
20
-	"github.com/moby/moby/v2/internal/testutils/labelstore"
21
-	"github.com/moby/moby/v2/internal/testutils/specialimage"
20
+	"github.com/moby/moby/v2/internal/testutil/labelstore"
21
+	"github.com/moby/moby/v2/internal/testutil/specialimage"
22 22
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
23 23
 	"gotest.tools/v3/assert"
24 24
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -11,7 +11,7 @@ import (
11 11
 	"github.com/containerd/containerd/v2/pkg/namespaces"
12 12
 	cerrdefs "github.com/containerd/errdefs"
13 13
 	"github.com/containerd/platforms"
14
-	"github.com/moby/moby/v2/internal/testutils/specialimage"
14
+	"github.com/moby/moby/v2/internal/testutil/specialimage"
15 15
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
16 16
 	"gotest.tools/v3/assert"
17 17
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -9,7 +9,7 @@ import (
9 9
 	"github.com/containerd/containerd/v2/pkg/namespaces"
10 10
 	cerrdefs "github.com/containerd/errdefs"
11 11
 	"github.com/containerd/platforms"
12
-	"github.com/moby/moby/v2/internal/testutils/specialimage"
12
+	"github.com/moby/moby/v2/internal/testutil/specialimage"
13 13
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
14 14
 	"gotest.tools/v3/assert"
15 15
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -12,7 +12,7 @@ import (
12 12
 	"github.com/google/go-cmp/cmp/cmpopts"
13 13
 	containertypes "github.com/moby/moby/api/types/container"
14 14
 	"github.com/moby/moby/v2/daemon/libnetwork/types"
15
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
15
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
16 16
 	"github.com/moby/sys/mount"
17 17
 	"github.com/moby/sys/mountinfo"
18 18
 	"github.com/vishvananda/netlink"
... ...
@@ -15,7 +15,7 @@ import (
15 15
 	"github.com/docker/distribution/manifest/ocischema"
16 16
 	"github.com/docker/distribution/manifest/schema2"
17 17
 	"github.com/google/go-cmp/cmp/cmpopts"
18
-	"github.com/moby/moby/v2/internal/testutils/labelstore"
18
+	"github.com/moby/moby/v2/internal/testutil/labelstore"
19 19
 	"github.com/opencontainers/go-digest"
20 20
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
21 21
 	"github.com/pkg/errors"
... ...
@@ -28,8 +28,8 @@ import (
28 28
 	"github.com/moby/moby/v2/daemon/libnetwork/portallocator"
29 29
 	"github.com/moby/moby/v2/daemon/libnetwork/portmapperapi"
30 30
 	"github.com/moby/moby/v2/daemon/libnetwork/types"
31
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
32
-	"github.com/moby/moby/v2/internal/testutils/storeutils"
31
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
32
+	"github.com/moby/moby/v2/internal/testutil/storeutils"
33 33
 	"github.com/vishvananda/netlink"
34 34
 	"github.com/vishvananda/netns"
35 35
 	"gotest.tools/v3/assert"
... ...
@@ -6,7 +6,7 @@ import (
6 6
 	"testing"
7 7
 
8 8
 	"github.com/moby/moby/v2/daemon/libnetwork/nlwrap"
9
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
9
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
10 10
 	"github.com/vishvananda/netlink"
11 11
 	"golang.org/x/sys/unix"
12 12
 	"gotest.tools/v3/assert"
... ...
@@ -14,7 +14,7 @@ import (
14 14
 	"github.com/moby/moby/v2/daemon/libnetwork/drivers/bridge/internal/firewaller"
15 15
 	"github.com/moby/moby/v2/daemon/libnetwork/iptables"
16 16
 	"github.com/moby/moby/v2/daemon/libnetwork/types"
17
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
17
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
18 18
 	"gotest.tools/v3/assert"
19 19
 	"gotest.tools/v3/golden"
20 20
 	"gotest.tools/v3/icmd"
... ...
@@ -9,7 +9,7 @@ import (
9 9
 
10 10
 	"github.com/moby/moby/v2/daemon/libnetwork/drivers/bridge/internal/firewaller"
11 11
 	"github.com/moby/moby/v2/daemon/libnetwork/iptables"
12
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
12
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
13 13
 	"gotest.tools/v3/assert"
14 14
 	is "gotest.tools/v3/assert/cmp"
15 15
 )
... ...
@@ -13,7 +13,7 @@ import (
13 13
 	"github.com/moby/moby/v2/daemon/libnetwork/drivers/bridge/internal/firewaller"
14 14
 	"github.com/moby/moby/v2/daemon/libnetwork/internal/nftables"
15 15
 	"github.com/moby/moby/v2/daemon/libnetwork/types"
16
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
16
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
17 17
 	"gotest.tools/v3/assert"
18 18
 	is "gotest.tools/v3/assert/cmp"
19 19
 	"gotest.tools/v3/golden"
... ...
@@ -8,8 +8,8 @@ import (
8 8
 	"github.com/moby/moby/v2/daemon/libnetwork/drvregistry"
9 9
 	"github.com/moby/moby/v2/daemon/libnetwork/netlabel"
10 10
 	"github.com/moby/moby/v2/daemon/libnetwork/nlwrap"
11
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
12
-	"github.com/moby/moby/v2/internal/testutils/storeutils"
11
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
12
+	"github.com/moby/moby/v2/internal/testutil/storeutils"
13 13
 	"gotest.tools/v3/assert"
14 14
 	is "gotest.tools/v3/assert/cmp"
15 15
 )
... ...
@@ -24,8 +24,8 @@ import (
24 24
 	"github.com/moby/moby/v2/daemon/libnetwork/portmappers/nat"
25 25
 	"github.com/moby/moby/v2/daemon/libnetwork/portmappers/routed"
26 26
 	"github.com/moby/moby/v2/daemon/libnetwork/types"
27
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
28
-	"github.com/moby/moby/v2/internal/testutils/storeutils"
27
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
28
+	"github.com/moby/moby/v2/internal/testutil/storeutils"
29 29
 	"github.com/sirupsen/logrus"
30 30
 	"github.com/vishvananda/netlink"
31 31
 	"gotest.tools/v3/assert"
... ...
@@ -9,7 +9,7 @@ import (
9 9
 	cerrdefs "github.com/containerd/errdefs"
10 10
 	"github.com/moby/moby/v2/daemon/libnetwork/netutils"
11 11
 	"github.com/moby/moby/v2/daemon/libnetwork/nlwrap"
12
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
12
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
13 13
 	"gotest.tools/v3/assert"
14 14
 	is "gotest.tools/v3/assert/cmp"
15 15
 )
... ...
@@ -9,7 +9,7 @@ import (
9 9
 	"testing"
10 10
 
11 11
 	"github.com/moby/moby/v2/daemon/libnetwork/drivers/bridge/internal/firewaller"
12
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
12
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
13 13
 	"gotest.tools/v3/assert"
14 14
 	is "gotest.tools/v3/assert/cmp"
15 15
 )
... ...
@@ -5,7 +5,7 @@ import (
5 5
 	"testing"
6 6
 
7 7
 	"github.com/moby/moby/v2/daemon/libnetwork/nlwrap"
8
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
8
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
9 9
 	"github.com/vishvananda/netlink"
10 10
 )
11 11
 
... ...
@@ -8,7 +8,7 @@ import (
8 8
 	"testing"
9 9
 
10 10
 	"github.com/moby/moby/v2/daemon/libnetwork/nlwrap"
11
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
11
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
12 12
 	"github.com/vishvananda/netlink"
13 13
 )
14 14
 
... ...
@@ -5,7 +5,7 @@ import (
5 5
 	"testing"
6 6
 
7 7
 	"github.com/moby/moby/v2/daemon/libnetwork/nlwrap"
8
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
8
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
9 9
 	"github.com/vishvananda/netlink"
10 10
 )
11 11
 
... ...
@@ -8,8 +8,7 @@ import (
8 8
 	"path/filepath"
9 9
 	"testing"
10 10
 
11
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
12
-
11
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
13 12
 	"github.com/vishvananda/netlink"
14 13
 	"gotest.tools/v3/assert"
15 14
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -6,7 +6,7 @@ import (
6 6
 	"testing"
7 7
 
8 8
 	"github.com/moby/moby/v2/daemon/libnetwork/driverapi"
9
-	"github.com/moby/moby/v2/internal/testutils/storeutils"
9
+	"github.com/moby/moby/v2/internal/testutil/storeutils"
10 10
 )
11 11
 
12 12
 const testNetworkType = "ipvlan"
... ...
@@ -6,7 +6,7 @@ import (
6 6
 	"testing"
7 7
 
8 8
 	"github.com/moby/moby/v2/daemon/libnetwork/driverapi"
9
-	"github.com/moby/moby/v2/internal/testutils/storeutils"
9
+	"github.com/moby/moby/v2/internal/testutil/storeutils"
10 10
 )
11 11
 
12 12
 const testNetworkType = "macvlan"
... ...
@@ -10,7 +10,7 @@ import (
10 10
 	"github.com/moby/moby/v2/daemon/libnetwork/driverapi"
11 11
 	"github.com/moby/moby/v2/daemon/libnetwork/netlabel"
12 12
 	"github.com/moby/moby/v2/daemon/libnetwork/types"
13
-	"github.com/moby/moby/v2/internal/testutils/storeutils"
13
+	"github.com/moby/moby/v2/internal/testutil/storeutils"
14 14
 	"gotest.tools/v3/assert"
15 15
 )
16 16
 
... ...
@@ -8,7 +8,7 @@ import (
8 8
 	"testing"
9 9
 
10 10
 	"github.com/moby/moby/v2/daemon/libnetwork/ipams/defaultipam"
11
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
11
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
12 12
 )
13 13
 
14 14
 func TestHostsEntries(t *testing.T) {
... ...
@@ -12,7 +12,7 @@ import (
12 12
 	"github.com/moby/moby/v2/daemon/libnetwork/drivers/bridge"
13 13
 	"github.com/moby/moby/v2/daemon/libnetwork/internal/nftables"
14 14
 	"github.com/moby/moby/v2/daemon/libnetwork/iptables"
15
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
15
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
16 16
 	"gotest.tools/v3/assert"
17 17
 	is "gotest.tools/v3/assert/cmp"
18 18
 	"gotest.tools/v3/golden"
... ...
@@ -5,7 +5,7 @@ import (
5 5
 	"os"
6 6
 	"testing"
7 7
 
8
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
8
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
9 9
 	"gotest.tools/v3/assert"
10 10
 	is "gotest.tools/v3/assert/cmp"
11 11
 	"gotest.tools/v3/golden"
... ...
@@ -11,7 +11,7 @@ import (
11 11
 	"strings"
12 12
 	"testing"
13 13
 
14
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
14
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
15 15
 	"golang.org/x/sync/errgroup"
16 16
 	"gotest.tools/v3/assert"
17 17
 	"gotest.tools/v3/skip"
... ...
@@ -327,8 +327,10 @@ func TestFlushChain(t *testing.T) {
327 327
 	assert.NilError(t, err)
328 328
 
329 329
 	// Add a rule to the chain
330
-	rule := Rule{IPVer: IPv4, Table: table, Chain: chain,
331
-		Args: []string{"-j", "ACCEPT"}}
330
+	rule := Rule{
331
+		IPVer: IPv4, Table: table, Chain: chain,
332
+		Args: []string{"-j", "ACCEPT"},
333
+	}
332 334
 	assert.NilError(t, rule.Insert())
333 335
 
334 336
 	// Flush the chain
... ...
@@ -22,7 +22,7 @@ import (
22 22
 	"github.com/moby/moby/v2/daemon/libnetwork/netutils"
23 23
 	"github.com/moby/moby/v2/daemon/libnetwork/scope"
24 24
 	"github.com/moby/moby/v2/daemon/libnetwork/types"
25
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
25
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
26 26
 	"gotest.tools/v3/assert"
27 27
 	is "gotest.tools/v3/assert/cmp"
28 28
 	"gotest.tools/v3/skip"
... ...
@@ -29,7 +29,7 @@ import (
29 29
 	"github.com/moby/moby/v2/daemon/libnetwork/options"
30 30
 	"github.com/moby/moby/v2/daemon/libnetwork/osl"
31 31
 	"github.com/moby/moby/v2/daemon/libnetwork/types"
32
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
32
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
33 33
 	"github.com/moby/moby/v2/pkg/plugins"
34 34
 	"github.com/moby/sys/reexec"
35 35
 	"github.com/pkg/errors"
... ...
@@ -10,7 +10,7 @@ import (
10 10
 
11 11
 	"github.com/google/go-cmp/cmp/cmpopts"
12 12
 	"github.com/moby/moby/v2/daemon/libnetwork/internal/netiputil"
13
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
13
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
14 14
 	"github.com/vishvananda/netlink"
15 15
 	"gotest.tools/v3/assert"
16 16
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -15,7 +15,7 @@ import (
15 15
 	"github.com/moby/moby/v2/daemon/libnetwork/nlwrap"
16 16
 	"github.com/moby/moby/v2/daemon/libnetwork/ns"
17 17
 	"github.com/moby/moby/v2/daemon/libnetwork/types"
18
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
18
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
19 19
 	"github.com/vishvananda/netlink"
20 20
 	"github.com/vishvananda/netlink/nl"
21 21
 	"github.com/vishvananda/netns"
... ...
@@ -12,7 +12,7 @@ import (
12 12
 	"github.com/containerd/log"
13 13
 	"github.com/miekg/dns"
14 14
 	"github.com/moby/moby/v2/daemon/libnetwork/types"
15
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
15
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
16 16
 	"github.com/sirupsen/logrus"
17 17
 	"gotest.tools/v3/assert"
18 18
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -10,7 +10,7 @@ import (
10 10
 	"github.com/miekg/dns"
11 11
 	"github.com/moby/moby/v2/daemon/libnetwork/config"
12 12
 	"github.com/moby/moby/v2/daemon/libnetwork/ipamutils"
13
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
13
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
14 14
 )
15 15
 
16 16
 // test only works on linux
... ...
@@ -14,7 +14,7 @@ import (
14 14
 	"github.com/moby/moby/v2/daemon/libnetwork/ipamutils"
15 15
 	"github.com/moby/moby/v2/daemon/libnetwork/netlabel"
16 16
 	"github.com/moby/moby/v2/daemon/libnetwork/options"
17
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
17
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
18 18
 	"gotest.tools/v3/assert"
19 19
 	is "gotest.tools/v3/assert/cmp"
20 20
 )
... ...
@@ -9,7 +9,7 @@ import (
9 9
 
10 10
 	"github.com/moby/moby/v2/daemon/libnetwork/config"
11 11
 	"github.com/moby/moby/v2/daemon/libnetwork/ipamutils"
12
-	"github.com/moby/moby/v2/internal/testutils/netnsutils"
12
+	"github.com/moby/moby/v2/internal/testutil/netnsutils"
13 13
 	"gotest.tools/v3/assert"
14 14
 )
15 15
 
... ...
@@ -13,7 +13,7 @@ import (
13 13
 	"github.com/moby/moby/api/types/container"
14 14
 	"github.com/moby/moby/api/types/image"
15 15
 	"github.com/moby/moby/v2/integration-cli/cli"
16
-	"github.com/moby/moby/v2/internal/testutils/specialimage"
16
+	"github.com/moby/moby/v2/internal/testutil/specialimage"
17 17
 	"gotest.tools/v3/assert"
18 18
 	is "gotest.tools/v3/assert/cmp"
19 19
 	"gotest.tools/v3/icmd"
... ...
@@ -32,7 +32,7 @@ import (
32 32
 	"github.com/moby/moby/v2/internal/testutil"
33 33
 	testdaemon "github.com/moby/moby/v2/internal/testutil/daemon"
34 34
 	"github.com/moby/moby/v2/internal/testutil/fakecontext"
35
-	"github.com/moby/moby/v2/internal/testutils/specialimage"
35
+	"github.com/moby/moby/v2/internal/testutil/specialimage"
36 36
 	"github.com/moby/sys/mountinfo"
37 37
 	"gotest.tools/v3/assert"
38 38
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -13,7 +13,7 @@ import (
13 13
 	"github.com/moby/moby/api/types/image"
14 14
 	"github.com/moby/moby/v2/integration-cli/cli"
15 15
 	"github.com/moby/moby/v2/integration-cli/cli/build"
16
-	"github.com/moby/moby/v2/internal/testutils/specialimage"
16
+	"github.com/moby/moby/v2/internal/testutil/specialimage"
17 17
 	"gotest.tools/v3/assert"
18 18
 	is "gotest.tools/v3/assert/cmp"
19 19
 	"gotest.tools/v3/icmd"
... ...
@@ -21,7 +21,7 @@ import (
21 21
 	"github.com/moby/moby/v2/integration-cli/cli"
22 22
 	"github.com/moby/moby/v2/integration-cli/daemon"
23 23
 	"github.com/moby/moby/v2/internal/testutil"
24
-	"github.com/moby/moby/v2/internal/testutils/specialimage"
24
+	"github.com/moby/moby/v2/internal/testutil/specialimage"
25 25
 	"gotest.tools/v3/assert"
26 26
 	is "gotest.tools/v3/assert/cmp"
27 27
 	"gotest.tools/v3/icmd"
... ...
@@ -7,7 +7,7 @@ import (
7 7
 
8 8
 	"github.com/moby/moby/client"
9 9
 	iimage "github.com/moby/moby/v2/integration/internal/image"
10
-	"github.com/moby/moby/v2/internal/testutils/specialimage"
10
+	"github.com/moby/moby/v2/internal/testutil/specialimage"
11 11
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
12 12
 	"gotest.tools/v3/assert"
13 13
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -17,7 +17,7 @@ import (
17 17
 	iimage "github.com/moby/moby/v2/integration/internal/image"
18 18
 	"github.com/moby/moby/v2/internal/testutil"
19 19
 	"github.com/moby/moby/v2/internal/testutil/daemon"
20
-	"github.com/moby/moby/v2/internal/testutils/specialimage"
20
+	"github.com/moby/moby/v2/internal/testutil/specialimage"
21 21
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
22 22
 	"gotest.tools/v3/assert"
23 23
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -8,7 +8,7 @@ import (
8 8
 	"github.com/moby/moby/api/types/image"
9 9
 	"github.com/moby/moby/client"
10 10
 	iimage "github.com/moby/moby/v2/integration/internal/image"
11
-	"github.com/moby/moby/v2/internal/testutils/specialimage"
11
+	"github.com/moby/moby/v2/internal/testutil/specialimage"
12 12
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
13 13
 	"gotest.tools/v3/assert"
14 14
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -11,7 +11,7 @@ import (
11 11
 	iimage "github.com/moby/moby/v2/integration/internal/image"
12 12
 	"github.com/moby/moby/v2/internal/testutil"
13 13
 	"github.com/moby/moby/v2/internal/testutil/daemon"
14
-	"github.com/moby/moby/v2/internal/testutils/specialimage"
14
+	"github.com/moby/moby/v2/internal/testutil/specialimage"
15 15
 	"gotest.tools/v3/assert"
16 16
 	is "gotest.tools/v3/assert/cmp"
17 17
 	"gotest.tools/v3/skip"
... ...
@@ -11,7 +11,7 @@ import (
11 11
 	"github.com/moby/moby/client"
12 12
 	"github.com/moby/moby/v2/integration/internal/container"
13 13
 	iimage "github.com/moby/moby/v2/integration/internal/image"
14
-	"github.com/moby/moby/v2/internal/testutils/specialimage"
14
+	"github.com/moby/moby/v2/internal/testutil/specialimage"
15 15
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
16 16
 	"gotest.tools/v3/assert"
17 17
 	is "gotest.tools/v3/assert/cmp"
... ...
@@ -20,9 +20,9 @@ import (
20 20
 	"github.com/moby/moby/v2/integration/internal/build"
21 21
 	"github.com/moby/moby/v2/integration/internal/container"
22 22
 	iimage "github.com/moby/moby/v2/integration/internal/image"
23
+	"github.com/moby/moby/v2/internal/testutil"
23 24
 	"github.com/moby/moby/v2/internal/testutil/fakecontext"
24
-	"github.com/moby/moby/v2/internal/testutils"
25
-	"github.com/moby/moby/v2/internal/testutils/specialimage"
25
+	"github.com/moby/moby/v2/internal/testutil/specialimage"
26 26
 	"github.com/opencontainers/go-digest"
27 27
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
28 28
 	"gotest.tools/v3/assert"
... ...
@@ -174,7 +174,7 @@ func TestSaveOCI(t *testing.T) {
174 174
 					f, err := tarfs.Open(layerPath)
175 175
 					assert.NilError(t, err)
176 176
 
177
-					layerDigest, err := testutils.UncompressedTarDigest(f)
177
+					layerDigest, err := testutil.UncompressedTarDigest(f)
178 178
 					f.Close()
179 179
 
180 180
 					assert.NilError(t, err)
... ...
@@ -12,7 +12,7 @@ import (
12 12
 	"github.com/moby/go-archive"
13 13
 	"github.com/moby/moby/client"
14 14
 	"github.com/moby/moby/client/pkg/jsonmessage"
15
-	"github.com/moby/moby/v2/internal/testutils/specialimage"
15
+	"github.com/moby/moby/v2/internal/testutil/specialimage"
16 16
 	"gotest.tools/v3/assert"
17 17
 )
18 18
 
19 19
new file mode 100644
... ...
@@ -0,0 +1,24 @@
0
+package testutil
1
+
2
+import (
3
+	"io"
4
+
5
+	"github.com/moby/go-archive/compression"
6
+	"github.com/opencontainers/go-digest"
7
+)
8
+
9
+// UncompressedTarDigest returns the canonical digest of the uncompressed tar stream.
10
+func UncompressedTarDigest(compressedTar io.Reader) (digest.Digest, error) {
11
+	rd, err := compression.DecompressStream(compressedTar)
12
+	if err != nil {
13
+		return "", err
14
+	}
15
+
16
+	defer rd.Close()
17
+
18
+	digester := digest.Canonical.Digester()
19
+	if _, err := io.Copy(digester.Hash(), rd); err != nil {
20
+		return "", err
21
+	}
22
+	return digester.Digest(), nil
23
+}
0 24
new file mode 100644
... ...
@@ -0,0 +1,52 @@
0
+package labelstore
1
+
2
+import (
3
+	"sync"
4
+
5
+	"github.com/opencontainers/go-digest"
6
+)
7
+
8
+type InMemory struct {
9
+	mu     sync.Mutex
10
+	labels map[digest.Digest]map[string]string
11
+}
12
+
13
+// Get returns all the labels for the given digest
14
+func (s *InMemory) Get(dgst digest.Digest) (map[string]string, error) {
15
+	s.mu.Lock()
16
+	labels := s.labels[dgst]
17
+	s.mu.Unlock()
18
+	return labels, nil
19
+}
20
+
21
+// Set sets all the labels for a given digest
22
+func (s *InMemory) Set(dgst digest.Digest, labels map[string]string) error {
23
+	s.mu.Lock()
24
+	if s.labels == nil {
25
+		s.labels = make(map[digest.Digest]map[string]string)
26
+	}
27
+	s.labels[dgst] = labels
28
+	s.mu.Unlock()
29
+	return nil
30
+}
31
+
32
+// Update replaces the given labels for a digest,
33
+// a key with an empty value removes a label.
34
+func (s *InMemory) Update(dgst digest.Digest, update map[string]string) (map[string]string, error) {
35
+	s.mu.Lock()
36
+	defer s.mu.Unlock()
37
+
38
+	labels, ok := s.labels[dgst]
39
+	if !ok {
40
+		labels = map[string]string{}
41
+	}
42
+	for k, v := range update {
43
+		labels[k] = v
44
+	}
45
+	if s.labels == nil {
46
+		s.labels = map[digest.Digest]map[string]string{}
47
+	}
48
+	s.labels[dgst] = labels
49
+
50
+	return labels, nil
51
+}
0 52
new file mode 100644
... ...
@@ -0,0 +1,10 @@
0
+package testutil
1
+
2
+import "testing"
3
+
4
+// Logger is used to log non-fatal messages during tests.
5
+type Logger interface {
6
+	Logf(format string, args ...any)
7
+}
8
+
9
+var _ Logger = (*testing.T)(nil)
0 10
new file mode 100644
... ...
@@ -0,0 +1,200 @@
0
+//go:build linux || freebsd
1
+
2
+package netnsutils
3
+
4
+import (
5
+	"fmt"
6
+	"runtime"
7
+	"strconv"
8
+	"testing"
9
+
10
+	"github.com/moby/moby/v2/daemon/libnetwork/ns"
11
+	"github.com/moby/moby/v2/internal/testutil"
12
+	"github.com/pkg/errors"
13
+	"github.com/vishvananda/netns"
14
+	"golang.org/x/sys/unix"
15
+)
16
+
17
+// OSContext is a handle to a test OS context.
18
+type OSContext struct {
19
+	origNS, newNS netns.NsHandle
20
+
21
+	tid    int
22
+	caller string // The file:line where SetupTestOSContextEx was called, for interpolating into error messages.
23
+}
24
+
25
+// SetupTestOSContext joins the current goroutine to a new network namespace,
26
+// and returns its associated teardown function.
27
+//
28
+// Example usage:
29
+//
30
+//	defer SetupTestOSContext(t)()
31
+func SetupTestOSContext(t *testing.T) func() {
32
+	c := SetupTestOSContextEx(t)
33
+	return func() { c.Cleanup(t) }
34
+}
35
+
36
+// SetupTestOSContextEx joins the current goroutine to a new network namespace.
37
+//
38
+// Compared to [SetupTestOSContext], this function allows goroutines to be
39
+// spawned which are associated with the same OS context via the returned
40
+// OSContext value.
41
+//
42
+// Example usage:
43
+//
44
+//	c := SetupTestOSContext(t)
45
+//	defer c.Cleanup(t)
46
+func SetupTestOSContextEx(t *testing.T) *OSContext {
47
+	runtime.LockOSThread()
48
+	origNS, err := netns.Get()
49
+	if err != nil {
50
+		runtime.UnlockOSThread()
51
+		t.Fatalf("Failed to open initial netns: %v", err)
52
+	}
53
+
54
+	c := OSContext{
55
+		tid:    unix.Gettid(),
56
+		origNS: origNS,
57
+	}
58
+	c.newNS, err = netns.New()
59
+	if err != nil {
60
+		// netns.New() is not atomic: it could have encountered an error
61
+		// after unsharing the current thread's network namespace.
62
+		c.restore(t)
63
+		t.Fatalf("Failed to enter netns: %v", err)
64
+	}
65
+
66
+	// Since we are switching to a new test namespace make
67
+	// sure to re-initialize initNs context
68
+	ns.Init()
69
+
70
+	nl := ns.NlHandle()
71
+	lo, err := nl.LinkByName("lo")
72
+	if err != nil {
73
+		c.restore(t)
74
+		t.Fatalf("Failed to get handle to loopback interface 'lo' in new netns: %v", err)
75
+	}
76
+	if err := nl.LinkSetUp(lo); err != nil {
77
+		c.restore(t)
78
+		t.Fatalf("Failed to enable loopback interface in new netns: %v", err)
79
+	}
80
+
81
+	_, file, line, ok := runtime.Caller(0)
82
+	if ok {
83
+		c.caller = file + ":" + strconv.Itoa(line)
84
+	}
85
+
86
+	return &c
87
+}
88
+
89
+// Cleanup tears down the OS context. It must be called from the same goroutine
90
+// as the [SetupTestOSContextEx] call which returned c.
91
+//
92
+// Explicit cleanup is required as (*testing.T).Cleanup() makes no guarantees
93
+// about which goroutine the cleanup functions are invoked on.
94
+func (c *OSContext) Cleanup(t *testing.T) {
95
+	t.Helper()
96
+	if unix.Gettid() != c.tid {
97
+		t.Fatalf("c.Cleanup() must be called from the same goroutine as SetupTestOSContextEx() (%s)", c.caller)
98
+	}
99
+	if err := c.newNS.Close(); err != nil {
100
+		t.Logf("Warning: netns closing failed (%v)", err)
101
+	}
102
+	c.restore(t)
103
+	ns.Init()
104
+}
105
+
106
+func (c *OSContext) restore(t *testing.T) {
107
+	t.Helper()
108
+	if err := netns.Set(c.origNS); err != nil {
109
+		t.Logf("Warning: failed to restore thread netns (%v)", err)
110
+	} else {
111
+		runtime.UnlockOSThread()
112
+	}
113
+
114
+	if err := c.origNS.Close(); err != nil {
115
+		t.Logf("Warning: netns closing failed (%v)", err)
116
+	}
117
+}
118
+
119
+// Set sets the OS context of the calling goroutine to c and returns a teardown
120
+// function to restore the calling goroutine's OS context and release resources.
121
+// The teardown function accepts an optional Logger argument.
122
+//
123
+// This is a lower-level interface which is less ergonomic than c.Go() but more
124
+// composable with other goroutine-spawning utilities such as [sync.WaitGroup]
125
+// or [golang.org/x/sync/errgroup.Group].
126
+//
127
+// Example usage:
128
+//
129
+//	func TestFoo(t *testing.T) {
130
+//		osctx := testutils.SetupTestOSContextEx(t)
131
+//		defer osctx.Cleanup(t)
132
+//		var eg errgroup.Group
133
+//		eg.Go(func() error {
134
+//			teardown, err := osctx.Set()
135
+//			if err != nil {
136
+//				return err
137
+//			}
138
+//			defer teardown(t)
139
+//			// ...
140
+//		})
141
+//		if err := eg.Wait(); err != nil {
142
+//			t.Fatalf("%+v", err)
143
+//		}
144
+//	}
145
+func (c *OSContext) Set() (func(testutil.Logger), error) {
146
+	runtime.LockOSThread()
147
+	orig, err := netns.Get()
148
+	if err != nil {
149
+		runtime.UnlockOSThread()
150
+		return nil, errors.Wrap(err, "failed to open initial netns for goroutine")
151
+	}
152
+	if err := errors.WithStack(netns.Set(c.newNS)); err != nil {
153
+		runtime.UnlockOSThread()
154
+		return nil, errors.Wrap(err, "failed to set goroutine network namespace")
155
+	}
156
+
157
+	tid := unix.Gettid()
158
+	_, file, line, callerOK := runtime.Caller(0)
159
+
160
+	return func(log testutil.Logger) {
161
+		if unix.Gettid() != tid {
162
+			msg := "teardown function must be called from the same goroutine as c.Set()"
163
+			if callerOK {
164
+				msg += fmt.Sprintf(" (%s:%d)", file, line)
165
+			}
166
+			panic(msg)
167
+		}
168
+
169
+		if err := netns.Set(orig); err != nil && log != nil {
170
+			log.Logf("Warning: failed to restore goroutine thread netns (%v)", err)
171
+		} else {
172
+			runtime.UnlockOSThread()
173
+		}
174
+
175
+		if err := orig.Close(); err != nil && log != nil {
176
+			log.Logf("Warning: netns closing failed (%v)", err)
177
+		}
178
+	}, nil
179
+}
180
+
181
+// Go starts running fn in a new goroutine inside the test OS context.
182
+func (c *OSContext) Go(t *testing.T, fn func()) {
183
+	t.Helper()
184
+	errCh := make(chan error, 1)
185
+	go func() {
186
+		teardown, err := c.Set()
187
+		if err != nil {
188
+			errCh <- err
189
+			return
190
+		}
191
+		defer teardown(t)
192
+		close(errCh)
193
+		fn()
194
+	}()
195
+
196
+	if err := <-errCh; err != nil {
197
+		t.Fatalf("%+v", err)
198
+	}
199
+}
0 200
new file mode 100644
... ...
@@ -0,0 +1,8 @@
0
+package netnsutils
1
+
2
+import "testing"
3
+
4
+// SetupTestOSContext is a no-op on Windows.
5
+func SetupTestOSContext(*testing.T) func() {
6
+	return func() {}
7
+}
0 8
new file mode 100644
... ...
@@ -0,0 +1,43 @@
0
+package netnsutils
1
+
2
+import (
3
+	"errors"
4
+	"syscall"
5
+	"testing"
6
+
7
+	"github.com/vishvananda/netns"
8
+	"golang.org/x/sys/unix"
9
+	"gotest.tools/v3/assert"
10
+)
11
+
12
+// AssertSocketSameNetNS makes a best-effort attempt to assert that conn is in
13
+// the same network namespace as the current goroutine's thread.
14
+func AssertSocketSameNetNS(t testing.TB, conn syscall.Conn) {
15
+	t.Helper()
16
+
17
+	sc, err := conn.SyscallConn()
18
+	assert.NilError(t, err)
19
+	sc.Control(func(fd uintptr) {
20
+		srvnsfd, err := unix.IoctlRetInt(int(fd), unix.SIOCGSKNS)
21
+		if err != nil {
22
+			if errors.Is(err, unix.EPERM) {
23
+				t.Log("Cannot determine socket's network namespace. Do we have CAP_NET_ADMIN?")
24
+				return
25
+			}
26
+			if errors.Is(err, unix.ENOSYS) {
27
+				t.Log("Cannot query socket's network namespace due to missing kernel support.")
28
+				return
29
+			}
30
+			t.Fatal(err)
31
+		}
32
+		srvns := netns.NsHandle(srvnsfd)
33
+		defer srvns.Close()
34
+
35
+		curns, err := netns.Get()
36
+		assert.NilError(t, err)
37
+		defer curns.Close()
38
+		if !srvns.Equal(curns) {
39
+			t.Fatalf("Socket is in network namespace %s, but test goroutine is in %s", srvns, curns)
40
+		}
41
+	})
42
+}
0 43
new file mode 100644
... ...
@@ -0,0 +1,11 @@
0
+//go:build !linux
1
+
2
+package netnsutils
3
+
4
+import (
5
+	"syscall"
6
+	"testing"
7
+)
8
+
9
+// AssertSocketSameNetNS is a no-op on platforms other than Linux.
10
+func AssertSocketSameNetNS(t testing.TB, conn syscall.Conn) {}
0 11
new file mode 100644
... ...
@@ -0,0 +1,33 @@
0
+package specialimage
1
+
2
+import (
3
+	"github.com/containerd/platforms"
4
+	"github.com/distribution/reference"
5
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
6
+)
7
+
8
+// ConfigTarget creates an image index with an image config being used as an
9
+// image target instead of a manifest or index.
10
+func ConfigTarget(dir string) (*ocispec.Index, error) {
11
+	const imageRef = "config:latest"
12
+
13
+	ref, err := reference.ParseNormalizedNamed(imageRef)
14
+	if err != nil {
15
+		return nil, err
16
+	}
17
+
18
+	desc, err := writeJsonBlob(dir, ocispec.MediaTypeImageConfig, ocispec.Image{
19
+		Platform: platforms.MustParse("linux/amd64"),
20
+		Config: ocispec.ImageConfig{
21
+			Env: []string{"FOO=BAR"},
22
+		},
23
+	})
24
+	if err != nil {
25
+		return nil, err
26
+	}
27
+	desc.Annotations = map[string]string{
28
+		"io.containerd.image.name": ref.String(),
29
+	}
30
+
31
+	return ociImage(dir, ref, desc)
32
+}
0 33
new file mode 100644
... ...
@@ -0,0 +1,45 @@
0
+package specialimage
1
+
2
+import (
3
+	"os"
4
+	"path/filepath"
5
+	"strings"
6
+
7
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
8
+)
9
+
10
+const (
11
+	danglingImageManifestDigest = "sha256:16d365089e5c10e1673ee82ab5bba38ade9b763296ad918bd24b42a1156c5456" // #nosec G101 -- ignoring: Potential hardcoded credentials (gosec)
12
+	danglingImageConfigDigest   = "sha256:0df1207206e5288f4a989a2f13d1f5b3c4e70467702c1d5d21dfc9f002b7bd43" // #nosec G101 -- ignoring: Potential hardcoded credentials (gosec)
13
+)
14
+
15
+// Dangling creates an image with no layers and no tag.
16
+// It also has an extra org.mobyproject.test.specialimage=1 label set.
17
+// Layout: OCI.
18
+func Dangling(dir string) (*ocispec.Index, error) {
19
+	if err := os.WriteFile(filepath.Join(dir, "index.json"), []byte(`{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.docker.distribution.manifest.v2+json","digest":"sha256:16d365089e5c10e1673ee82ab5bba38ade9b763296ad918bd24b42a1156c5456","size":264,"annotations":{"org.opencontainers.image.created":"2023-05-19T08:00:44Z"},"platform":{"architecture":"amd64","os":"linux"}}]}`), 0o644); err != nil {
20
+		return nil, err
21
+	}
22
+
23
+	if err := os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(`[{"Config":"blobs/sha256/0df1207206e5288f4a989a2f13d1f5b3c4e70467702c1d5d21dfc9f002b7bd43","RepoTags":null,"Layers":null}]`), 0o644); err != nil {
24
+		return nil, err
25
+	}
26
+
27
+	if err := os.Mkdir(filepath.Join(dir, "blobs"), 0o755); err != nil {
28
+		return nil, err
29
+	}
30
+
31
+	blobsDir := filepath.Join(dir, "blobs", "sha256")
32
+	if err := os.Mkdir(blobsDir, 0o755); err != nil {
33
+		return nil, err
34
+	}
35
+
36
+	if err := os.WriteFile(filepath.Join(blobsDir, strings.TrimPrefix(danglingImageManifestDigest, "sha256:")), []byte(`{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:0df1207206e5288f4a989a2f13d1f5b3c4e70467702c1d5d21dfc9f002b7bd43","size":390},"layers":[]}`), 0o644); err != nil {
37
+		return nil, err
38
+	}
39
+	if err := os.WriteFile(filepath.Join(blobsDir, strings.TrimPrefix(danglingImageConfigDigest, "sha256:")), []byte(`{"architecture":"amd64","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"WorkingDir":"/","Labels":{"org.mobyproject.test.specialimage":"1"},"OnBuild":null},"created":null,"history":[{"created_by":"LABEL org.mobyproject.test.specialimage=1","comment":"buildkit.dockerfile.v0","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":null}}`), 0o644); err != nil {
40
+		return nil, err
41
+	}
42
+
43
+	return nil, nil
44
+}
0 45
new file mode 100644
... ...
@@ -0,0 +1,66 @@
0
+package specialimage
1
+
2
+import (
3
+	"io"
4
+	"os"
5
+	"path/filepath"
6
+
7
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
8
+)
9
+
10
+// EmptyFS builds an image with an empty rootfs.
11
+// Layout: Legacy Docker Archive
12
+// See https://github.com/moby/moby/pull/5262
13
+// and also https://github.com/moby/moby/issues/4242
14
+func EmptyFS(dir string) (*ocispec.Index, error) {
15
+	if err := os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(`[{"Config":"11f64303f0f7ffdc71f001788132bca5346831939a956e3e975c93267d89a16d.json","RepoTags":["emptyfs:latest"],"Layers":["511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/layer.tar"]}]`), 0o644); err != nil {
16
+		return nil, err
17
+	}
18
+
19
+	if err := os.Mkdir(filepath.Join(dir, "blobs"), 0o755); err != nil {
20
+		return nil, err
21
+	}
22
+
23
+	blobsDir := filepath.Join(dir, "511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158")
24
+	if err := os.Mkdir(blobsDir, 0o755); err != nil {
25
+		return nil, err
26
+	}
27
+
28
+	if err := os.WriteFile(filepath.Join(dir, "VERSION"), []byte(`1.0`), 0o644); err != nil {
29
+		return nil, err
30
+	}
31
+	if err := os.WriteFile(filepath.Join(dir, "repositories"), []byte(`{"emptyfs":{"latest":"511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158"}}`), 0o644); err != nil {
32
+		return nil, err
33
+	}
34
+	if err := os.WriteFile(filepath.Join(dir, "11f64303f0f7ffdc71f001788132bca5346831939a956e3e975c93267d89a16d.json"), []byte(`{"architecture":"x86_64","comment":"Imported from -","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":null,"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"created":"2013-06-13T14:03:50.821769-07:00","docker_version":"0.4.0","history":[{"created":"2013-06-13T14:03:50.821769-07:00","comment":"Imported from -"}],"rootfs":{"type":"layers","diff_ids":["sha256:84ff92691f909a05b224e1c56abb4864f01b4f8e3c854e4bb4c7baf1d3f6d652"]}}`), 0o644); err != nil {
35
+		return nil, err
36
+	}
37
+
38
+	if err := os.WriteFile(filepath.Join(blobsDir, "json"), []byte(`{"id":"511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158","comment":"Imported from -","created":"2013-06-13T14:03:50.821769-07:00","container_config":{"Hostname":"","Domainname":"","User":"","Memory":0,"MemorySwap":0,"CpuShares":0,"AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"PortSpecs":null,"ExposedPorts":null,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":null,"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"NetworkDisabled":false,"OnBuild":null},"docker_version":"0.4.0","architecture":"x86_64","Size":0}`+"\n"), 0o644); err != nil {
39
+		return nil, err
40
+	}
41
+
42
+	layerFile, err := os.OpenFile(filepath.Join(blobsDir, "layer.tar"), os.O_CREATE|os.O_WRONLY, 0o644)
43
+	if err != nil {
44
+		return nil, err
45
+	}
46
+	defer layerFile.Close()
47
+
48
+	// 10240 NUL bytes is a valid empty tar archive.
49
+	_, err = io.Copy(layerFile, io.LimitReader(zeroReader{}, 10240))
50
+	if err != nil {
51
+		return nil, err
52
+	}
53
+
54
+	return nil, nil
55
+}
56
+
57
+type zeroReader struct{}
58
+
59
+func (zeroReader) Read(p []byte) (int, error) {
60
+	l := len(p)
61
+	for idx := 0; idx < l; idx++ {
62
+		p[idx] = 0
63
+	}
64
+	return l, nil
65
+}
0 66
new file mode 100644
... ...
@@ -0,0 +1,25 @@
0
+package specialimage
1
+
2
+import (
3
+	"github.com/distribution/reference"
4
+	"github.com/opencontainers/image-spec/specs-go"
5
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
6
+)
7
+
8
+// EmptyIndex creates an image index with no manifests.
9
+// This is equivalent to `tianon/scratch:index`.
10
+func EmptyIndex(dir string) (*ocispec.Index, error) {
11
+	const imageRef = "emptyindex:latest"
12
+
13
+	index := ocispec.Index{
14
+		Versioned: specs.Versioned{SchemaVersion: 2},
15
+		MediaType: ocispec.MediaTypeImageIndex,
16
+		Manifests: []ocispec.Descriptor{},
17
+	}
18
+
19
+	ref, err := reference.ParseNormalizedNamed(imageRef)
20
+	if err != nil {
21
+		return nil, err
22
+	}
23
+	return multiPlatformImage(dir, ref, index)
24
+}
0 25
new file mode 100644
... ...
@@ -0,0 +1,7 @@
0
+package specialimage
1
+
2
+import (
3
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
4
+)
5
+
6
+type SpecialImageFunc func(string) (*ocispec.Index, error)
0 7
new file mode 100644
... ...
@@ -0,0 +1,218 @@
0
+package specialimage
1
+
2
+import (
3
+	"bytes"
4
+	"encoding/json"
5
+	"io"
6
+	"os"
7
+	"path/filepath"
8
+
9
+	"github.com/containerd/platforms"
10
+	"github.com/distribution/reference"
11
+	"github.com/google/uuid"
12
+	"github.com/moby/go-archive"
13
+	"github.com/opencontainers/go-digest"
14
+	"github.com/opencontainers/image-spec/specs-go"
15
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
16
+)
17
+
18
+type SingleFileLayer struct {
19
+	Name    string
20
+	Content []byte
21
+}
22
+
23
+func MultiLayer(dir string) (*ocispec.Index, error) {
24
+	return MultiLayerCustom(dir, "multilayer:latest", []SingleFileLayer{
25
+		{Name: "foo", Content: []byte("1")},
26
+		{Name: "bar", Content: []byte("2")},
27
+		{Name: "hello", Content: []byte("world")},
28
+	})
29
+}
30
+
31
+func MultiLayerCustom(dir string, imageRef string, layers []SingleFileLayer) (*ocispec.Index, error) {
32
+	var layerDescs []ocispec.Descriptor
33
+	var layerDgsts []digest.Digest
34
+	var layerBlobs []string
35
+	for _, layer := range layers {
36
+		layerDesc, err := writeLayerWithOneFile(dir, layer.Name, layer.Content)
37
+		if err != nil {
38
+			return nil, err
39
+		}
40
+
41
+		layerDescs = append(layerDescs, layerDesc)
42
+		layerDgsts = append(layerDgsts, layerDesc.Digest)
43
+		layerBlobs = append(layerBlobs, blobPath(layerDesc))
44
+	}
45
+
46
+	configDesc, err := writeJsonBlob(dir, ocispec.MediaTypeImageConfig, ocispec.Image{
47
+		Platform: platforms.DefaultSpec(),
48
+		Config: ocispec.ImageConfig{
49
+			Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
50
+		},
51
+		RootFS: ocispec.RootFS{
52
+			Type:    "layers",
53
+			DiffIDs: layerDgsts,
54
+		},
55
+	})
56
+	if err != nil {
57
+		return nil, err
58
+	}
59
+
60
+	manifest := ocispec.Manifest{
61
+		MediaType: ocispec.MediaTypeImageManifest,
62
+		Config:    configDesc,
63
+		Layers:    layerDescs,
64
+	}
65
+
66
+	legacyManifests := []manifestItem{
67
+		{
68
+			Config:   blobPath(configDesc),
69
+			RepoTags: []string{imageRef},
70
+			Layers:   layerBlobs,
71
+		},
72
+	}
73
+
74
+	ref, err := reference.ParseNormalizedNamed(imageRef)
75
+	if err != nil {
76
+		return nil, err
77
+	}
78
+	return singlePlatformImage(dir, ref, manifest, legacyManifests)
79
+}
80
+
81
+// Legacy manifest item (manifests.json)
82
+type manifestItem struct {
83
+	Config   string
84
+	RepoTags []string
85
+	Layers   []string
86
+}
87
+
88
+func singlePlatformImage(dir string, ref reference.Named, manifest ocispec.Manifest, legacyManifests []manifestItem) (*ocispec.Index, error) {
89
+	manifestDesc, err := writeJsonBlob(dir, ocispec.MediaTypeImageManifest, manifest)
90
+	if err != nil {
91
+		return nil, err
92
+	}
93
+
94
+	if ref != nil {
95
+		manifestDesc.Annotations = map[string]string{
96
+			"io.containerd.image.name": ref.String(),
97
+		}
98
+
99
+		if tagged, ok := ref.(reference.Tagged); ok {
100
+			manifestDesc.Annotations[ocispec.AnnotationRefName] = tagged.Tag()
101
+		}
102
+	}
103
+
104
+	if err := writeJson(legacyManifests, filepath.Join(dir, "manifest.json")); err != nil {
105
+		return nil, err
106
+	}
107
+
108
+	return ociImage(dir, ref, manifestDesc)
109
+}
110
+
111
+func ociImage(dir string, ref reference.Named, target ocispec.Descriptor) (*ocispec.Index, error) {
112
+	idx := ocispec.Index{
113
+		Versioned: specs.Versioned{SchemaVersion: 2},
114
+		MediaType: ocispec.MediaTypeImageIndex,
115
+		Manifests: []ocispec.Descriptor{target},
116
+	}
117
+	if err := writeJson(idx, filepath.Join(dir, "index.json")); err != nil {
118
+		return nil, err
119
+	}
120
+
121
+	err := os.WriteFile(filepath.Join(dir, "oci-layout"), []byte(`{"imageLayoutVersion": "1.0.0"}`), 0o644)
122
+	if err != nil {
123
+		return nil, err
124
+	}
125
+
126
+	return &idx, nil
127
+}
128
+
129
+func fileArchive(dir string, name string, content []byte) (io.ReadCloser, error) {
130
+	tmp, err := os.MkdirTemp("", "")
131
+	if err != nil {
132
+		return nil, err
133
+	}
134
+
135
+	if err := os.WriteFile(filepath.Join(tmp, name), content, 0o644); err != nil {
136
+		return nil, err
137
+	}
138
+
139
+	return archive.Tar(tmp, archive.Uncompressed)
140
+}
141
+
142
+func writeLayerWithOneFile(dir string, filename string, content []byte) (ocispec.Descriptor, error) {
143
+	rd, err := fileArchive(dir, filename, content)
144
+	if err != nil {
145
+		return ocispec.Descriptor{}, err
146
+	}
147
+	defer rd.Close()
148
+
149
+	return writeBlob(dir, ocispec.MediaTypeImageLayer, rd)
150
+}
151
+
152
+func writeJsonBlob(dir string, mt string, obj any) (ocispec.Descriptor, error) {
153
+	b, err := json.Marshal(obj)
154
+	if err != nil {
155
+		return ocispec.Descriptor{}, err
156
+	}
157
+
158
+	return writeBlob(dir, mt, bytes.NewReader(b))
159
+}
160
+
161
+func writeJson(obj any, path string) error {
162
+	b, err := json.Marshal(obj)
163
+	if err != nil {
164
+		return err
165
+	}
166
+
167
+	return os.WriteFile(path, b, 0o644)
168
+}
169
+
170
+func writeBlob(dir string, mt string, rd io.Reader) (_ ocispec.Descriptor, outErr error) {
171
+	digester := digest.Canonical.Digester()
172
+	hashTee := io.TeeReader(rd, digester.Hash())
173
+
174
+	blobsPath := filepath.Join(dir, "blobs", "sha256")
175
+	if err := os.MkdirAll(blobsPath, 0o755); err != nil {
176
+		return ocispec.Descriptor{}, err
177
+	}
178
+
179
+	tmpPath := filepath.Join(blobsPath, uuid.New().String())
180
+	file, err := os.Create(tmpPath)
181
+	if err != nil {
182
+		return ocispec.Descriptor{}, err
183
+	}
184
+
185
+	defer func() {
186
+		if outErr != nil {
187
+			file.Close()
188
+			os.Remove(tmpPath)
189
+		}
190
+	}()
191
+
192
+	if _, err := io.Copy(file, hashTee); err != nil {
193
+		return ocispec.Descriptor{}, err
194
+	}
195
+
196
+	digest := digester.Digest()
197
+
198
+	stat, err := os.Stat(tmpPath)
199
+	if err != nil {
200
+		return ocispec.Descriptor{}, err
201
+	}
202
+
203
+	file.Close()
204
+	if err := os.Rename(tmpPath, filepath.Join(blobsPath, digest.Encoded())); err != nil {
205
+		return ocispec.Descriptor{}, err
206
+	}
207
+
208
+	return ocispec.Descriptor{
209
+		MediaType: mt,
210
+		Digest:    digest,
211
+		Size:      stat.Size(),
212
+	}, nil
213
+}
214
+
215
+func blobPath(desc ocispec.Descriptor) string {
216
+	return "blobs/sha256/" + desc.Digest.Encoded()
217
+}
0 218
new file mode 100644
... ...
@@ -0,0 +1,33 @@
0
+package specialimage
1
+
2
+import (
3
+	"github.com/containerd/platforms"
4
+	"github.com/distribution/reference"
5
+	"github.com/opencontainers/image-spec/specs-go"
6
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
7
+)
8
+
9
+func MultiPlatform(dir string, imageRef string, imagePlatforms []ocispec.Platform) (*ocispec.Index, []ocispec.Descriptor, error) {
10
+	ref, err := reference.ParseNormalizedNamed(imageRef)
11
+	if err != nil {
12
+		return nil, nil, err
13
+	}
14
+
15
+	var descs []ocispec.Descriptor
16
+
17
+	for _, platform := range imagePlatforms {
18
+		ps := platforms.FormatAll(platform)
19
+		manifestDesc, _, err := oneLayerPlatformManifest(dir, platform, FileInLayer{Path: "bash", Content: []byte("layer-" + ps)})
20
+		if err != nil {
21
+			return nil, nil, err
22
+		}
23
+		descs = append(descs, manifestDesc)
24
+	}
25
+
26
+	idx, err := multiPlatformImage(dir, ref, ocispec.Index{
27
+		Versioned: specs.Versioned{SchemaVersion: 2},
28
+		MediaType: ocispec.MediaTypeImageIndex,
29
+		Manifests: descs,
30
+	})
31
+	return idx, descs, err
32
+}
0 33
new file mode 100644
... ...
@@ -0,0 +1,55 @@
0
+package specialimage
1
+
2
+import (
3
+	"github.com/containerd/platforms"
4
+	"github.com/distribution/reference"
5
+	"github.com/opencontainers/go-digest"
6
+	"github.com/opencontainers/image-spec/specs-go"
7
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
8
+)
9
+
10
+type PartialOpts struct {
11
+	Stored  []ocispec.Platform
12
+	Missing []ocispec.Platform
13
+}
14
+
15
+// PartialMultiPlatform creates an index with all platforms in storedPlatforms
16
+// and missingPlatforms. However, only the blobs of the storedPlatforms are
17
+// created and stored, while the missingPlatforms are only referenced in the
18
+// index.
19
+func PartialMultiPlatform(dir string, imageRef string, opts PartialOpts) (*ocispec.Index, []ocispec.Descriptor, error) {
20
+	ref, err := reference.ParseNormalizedNamed(imageRef)
21
+	if err != nil {
22
+		return nil, nil, err
23
+	}
24
+
25
+	var descs []ocispec.Descriptor
26
+
27
+	for _, platform := range opts.Stored {
28
+		ps := platforms.FormatAll(platform)
29
+		manifestDesc, _, err := oneLayerPlatformManifest(dir, platform, FileInLayer{Path: "bash", Content: []byte("layer-" + ps)})
30
+		if err != nil {
31
+			return nil, nil, err
32
+		}
33
+		descs = append(descs, manifestDesc)
34
+	}
35
+
36
+	for _, platform := range opts.Missing {
37
+		platformStr := platforms.FormatAll(platform)
38
+		dgst := digest.FromBytes([]byte(platformStr))
39
+
40
+		descs = append(descs, ocispec.Descriptor{
41
+			MediaType: ocispec.MediaTypeImageManifest,
42
+			Size:      128,
43
+			Platform:  &platform,
44
+			Digest:    dgst,
45
+		})
46
+	}
47
+
48
+	idx, err := multiPlatformImage(dir, ref, ocispec.Index{
49
+		Versioned: specs.Versioned{SchemaVersion: 2},
50
+		MediaType: ocispec.MediaTypeImageIndex,
51
+		Manifests: descs,
52
+	})
53
+	return idx, descs, err
54
+}
0 55
new file mode 100644
... ...
@@ -0,0 +1,109 @@
0
+package specialimage
1
+
2
+import (
3
+	"encoding/json"
4
+	"math/rand"
5
+	"os"
6
+	"path/filepath"
7
+	"strconv"
8
+
9
+	"github.com/distribution/reference"
10
+	"github.com/opencontainers/go-digest"
11
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
12
+)
13
+
14
+func RandomSinglePlatform(dir string, platform ocispec.Platform, source rand.Source) (*ocispec.Index, error) {
15
+	r := rand.New(source) //nolint:gosec // Ignore G404: Use of weak random number generator (math/rand instead of crypto/rand)
16
+
17
+	imageRef := "random-" + strconv.FormatInt(r.Int63(), 10) + ":latest"
18
+
19
+	layerCount := r.Intn(8)
20
+
21
+	var layers []ocispec.Descriptor
22
+	for i := 0; i < layerCount; i++ {
23
+		layerDesc, err := writeLayerWithOneFile(dir, "layer-"+strconv.Itoa(i), []byte(strconv.Itoa(i)))
24
+		if err != nil {
25
+			return nil, err
26
+		}
27
+		layers = append(layers, layerDesc)
28
+	}
29
+
30
+	configDesc, err := writeJsonBlob(dir, ocispec.MediaTypeImageConfig, ocispec.Image{
31
+		Platform: platform,
32
+		Config: ocispec.ImageConfig{
33
+			Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
34
+		},
35
+		RootFS: ocispec.RootFS{
36
+			Type:    "layers",
37
+			DiffIDs: layersToDigests(layers),
38
+		},
39
+	})
40
+	if err != nil {
41
+		return nil, err
42
+	}
43
+
44
+	manifest := ocispec.Manifest{
45
+		MediaType: ocispec.MediaTypeImageManifest,
46
+		Config:    configDesc,
47
+		Layers:    layers,
48
+	}
49
+
50
+	legacyManifests := []manifestItem{
51
+		{
52
+			Config:   blobPath(configDesc),
53
+			RepoTags: []string{imageRef},
54
+			Layers:   blobPaths(layers),
55
+		},
56
+	}
57
+
58
+	ref, err := reference.ParseNormalizedNamed(imageRef)
59
+	if err != nil {
60
+		return nil, err
61
+	}
62
+	return singlePlatformImage(dir, ref, manifest, legacyManifests)
63
+}
64
+
65
+func layersToDigests(layers []ocispec.Descriptor) []digest.Digest {
66
+	var digests []digest.Digest
67
+	for _, l := range layers {
68
+		digests = append(digests, l.Digest)
69
+	}
70
+	return digests
71
+}
72
+
73
+func blobPaths(descriptors []ocispec.Descriptor) []string {
74
+	var paths []string
75
+	for _, d := range descriptors {
76
+		paths = append(paths, blobPath(d))
77
+	}
78
+	return paths
79
+}
80
+
81
+func readJson(path string, v any) error {
82
+	content, err := os.ReadFile(path)
83
+	if err != nil {
84
+		return err
85
+	}
86
+	return json.Unmarshal(content, v)
87
+}
88
+
89
+func LegacyManifest(dir string, imageRef string, mfstDesc ocispec.Descriptor) error {
90
+	legacyManifests := []manifestItem{}
91
+
92
+	var mfst ocispec.Manifest
93
+	if err := readJson(filepath.Join(dir, blobPath(mfstDesc)), &mfst); err != nil {
94
+		return err
95
+	}
96
+
97
+	legacyManifests = append(legacyManifests, manifestItem{
98
+		Config:   blobPath(mfst.Config),
99
+		RepoTags: []string{imageRef},
100
+		Layers:   blobPaths(mfst.Layers),
101
+	})
102
+
103
+	if err := writeJson(legacyManifests, filepath.Join(dir, "manifest.json")); err != nil {
104
+		return err
105
+	}
106
+
107
+	return nil
108
+}
0 109
new file mode 100644
... ...
@@ -0,0 +1,39 @@
0
+package specialimage
1
+
2
+import (
3
+	"strings"
4
+
5
+	"github.com/distribution/reference"
6
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
7
+)
8
+
9
+// TextPlain creates an non-container image that only contains a text/plain blob.
10
+func TextPlain(dir string) (*ocispec.Index, error) {
11
+	ref, err := reference.ParseNormalizedNamed("tianon/test:text-plain")
12
+	if err != nil {
13
+		return nil, err
14
+	}
15
+
16
+	emptyJsonDesc, err := writeBlob(dir, "text/plain", strings.NewReader("{}"))
17
+	if err != nil {
18
+		return nil, err
19
+	}
20
+
21
+	configDesc := emptyJsonDesc
22
+	configDesc.MediaType = "application/vnd.oci.empty.v1+json"
23
+
24
+	desc, err := writeJsonBlob(dir, ocispec.MediaTypeImageManifest, ocispec.Manifest{
25
+		Config: configDesc,
26
+		Layers: []ocispec.Descriptor{
27
+			emptyJsonDesc,
28
+		},
29
+	})
30
+	if err != nil {
31
+		return nil, err
32
+	}
33
+	desc.Annotations = map[string]string{
34
+		"io.containerd.image.name": ref.String(),
35
+	}
36
+
37
+	return ociImage(dir, nil, desc)
38
+}
0 39
new file mode 100644
... ...
@@ -0,0 +1,113 @@
0
+package specialimage
1
+
2
+import (
3
+	"os"
4
+	"path/filepath"
5
+
6
+	"github.com/containerd/platforms"
7
+	"github.com/distribution/reference"
8
+	"github.com/opencontainers/go-digest"
9
+	"github.com/opencontainers/image-spec/specs-go"
10
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
11
+)
12
+
13
+func TwoPlatform(dir string) (*ocispec.Index, error) {
14
+	const imageRef = "twoplatform:latest"
15
+	ref, err := reference.ParseNormalizedNamed(imageRef)
16
+	if err != nil {
17
+		return nil, err
18
+	}
19
+
20
+	manifest1Desc, _, err := oneLayerPlatformManifest(dir, platforms.MustParse("linux/amd64"), FileInLayer{Path: "bash", Content: []byte("layer1")})
21
+	if err != nil {
22
+		return nil, err
23
+	}
24
+
25
+	manifest2Desc, _, err := oneLayerPlatformManifest(dir, platforms.MustParse("linux/arm64"), FileInLayer{Path: "bash", Content: []byte("layer2")})
26
+	if err != nil {
27
+		return nil, err
28
+	}
29
+
30
+	return multiPlatformImage(dir, ref, ocispec.Index{
31
+		Versioned: specs.Versioned{SchemaVersion: 2},
32
+		MediaType: ocispec.MediaTypeImageIndex,
33
+		Manifests: []ocispec.Descriptor{manifest1Desc, manifest2Desc},
34
+	})
35
+}
36
+
37
+type FileInLayer struct {
38
+	Path    string
39
+	Content []byte
40
+}
41
+
42
+func oneLayerPlatformManifest(dir string, platform ocispec.Platform, f FileInLayer) (ocispec.Descriptor, manifestItem, error) {
43
+	layerDesc, err := writeLayerWithOneFile(dir, f.Path, f.Content)
44
+	if err != nil {
45
+		return ocispec.Descriptor{}, manifestItem{}, err
46
+	}
47
+
48
+	img := ocispec.Image{
49
+		Platform: platform,
50
+		Config: ocispec.ImageConfig{
51
+			Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
52
+		},
53
+		RootFS: ocispec.RootFS{
54
+			Type:    "layers",
55
+			DiffIDs: []digest.Digest{layerDesc.Digest},
56
+		},
57
+	}
58
+
59
+	configDesc, err := writeJsonBlob(dir, ocispec.MediaTypeImageConfig, img)
60
+	if err != nil {
61
+		return ocispec.Descriptor{}, manifestItem{}, err
62
+	}
63
+
64
+	manifestDesc, err := writeJsonBlob(dir, ocispec.MediaTypeImageManifest, ocispec.Manifest{
65
+		MediaType: ocispec.MediaTypeImageManifest,
66
+		Config:    configDesc,
67
+		Layers:    []ocispec.Descriptor{layerDesc},
68
+	})
69
+	if err != nil {
70
+		return ocispec.Descriptor{}, manifestItem{}, err
71
+	}
72
+	manifestDesc.Platform = &platform
73
+
74
+	return manifestDesc, manifestItem{
75
+		Config: blobPath(configDesc),
76
+		Layers: []string{blobPath(layerDesc)},
77
+	}, nil
78
+}
79
+
80
+func multiPlatformImage(dir string, ref reference.Named, target ocispec.Index) (*ocispec.Index, error) {
81
+	targetDesc, err := writeJsonBlob(dir, ocispec.MediaTypeImageIndex, target)
82
+	if err != nil {
83
+		return nil, err
84
+	}
85
+
86
+	if ref != nil {
87
+		targetDesc.Annotations = map[string]string{
88
+			"io.containerd.image.name": ref.String(),
89
+		}
90
+
91
+		if tagged, ok := ref.(reference.Tagged); ok {
92
+			targetDesc.Annotations[ocispec.AnnotationRefName] = tagged.Tag()
93
+		}
94
+	}
95
+
96
+	index := ocispec.Index{
97
+		Versioned: specs.Versioned{SchemaVersion: 2},
98
+		MediaType: ocispec.MediaTypeImageIndex,
99
+		Manifests: []ocispec.Descriptor{targetDesc},
100
+	}
101
+
102
+	if err := writeJson(index, filepath.Join(dir, "index.json")); err != nil {
103
+		return nil, err
104
+	}
105
+
106
+	err = os.WriteFile(filepath.Join(dir, "oci-layout"), []byte(`{"imageLayoutVersion": "1.0.0"}`), 0o644)
107
+	if err != nil {
108
+		return nil, err
109
+	}
110
+
111
+	return &index, nil
112
+}
0 113
new file mode 100644
... ...
@@ -0,0 +1,20 @@
0
+package storeutils
1
+
2
+import (
3
+	"testing"
4
+
5
+	"github.com/moby/moby/v2/daemon/libnetwork/datastore"
6
+	"gotest.tools/v3/assert"
7
+)
8
+
9
+// NewTempStore creates a new temporary libnetwork store for testing purposes.
10
+// The store is created in a temporary directory that is cleaned up when the
11
+// test finishes.
12
+func NewTempStore(t *testing.T) *datastore.Store {
13
+	t.Helper()
14
+
15
+	ds, err := datastore.New(t.TempDir(), "libnetwork")
16
+	assert.NilError(t, err)
17
+
18
+	return ds
19
+}
0 20
deleted file mode 100644
... ...
@@ -1,24 +0,0 @@
1
-package testutils
2
-
3
-import (
4
-	"io"
5
-
6
-	"github.com/moby/go-archive/compression"
7
-	"github.com/opencontainers/go-digest"
8
-)
9
-
10
-// UncompressedTarDigest returns the canonical digest of the uncompressed tar stream.
11
-func UncompressedTarDigest(compressedTar io.Reader) (digest.Digest, error) {
12
-	rd, err := compression.DecompressStream(compressedTar)
13
-	if err != nil {
14
-		return "", err
15
-	}
16
-
17
-	defer rd.Close()
18
-
19
-	digester := digest.Canonical.Digester()
20
-	if _, err := io.Copy(digester.Hash(), rd); err != nil {
21
-		return "", err
22
-	}
23
-	return digester.Digest(), nil
24
-}
25 1
deleted file mode 100644
... ...
@@ -1,52 +0,0 @@
1
-package labelstore
2
-
3
-import (
4
-	"sync"
5
-
6
-	"github.com/opencontainers/go-digest"
7
-)
8
-
9
-type InMemory struct {
10
-	mu     sync.Mutex
11
-	labels map[digest.Digest]map[string]string
12
-}
13
-
14
-// Get returns all the labels for the given digest
15
-func (s *InMemory) Get(dgst digest.Digest) (map[string]string, error) {
16
-	s.mu.Lock()
17
-	labels := s.labels[dgst]
18
-	s.mu.Unlock()
19
-	return labels, nil
20
-}
21
-
22
-// Set sets all the labels for a given digest
23
-func (s *InMemory) Set(dgst digest.Digest, labels map[string]string) error {
24
-	s.mu.Lock()
25
-	if s.labels == nil {
26
-		s.labels = make(map[digest.Digest]map[string]string)
27
-	}
28
-	s.labels[dgst] = labels
29
-	s.mu.Unlock()
30
-	return nil
31
-}
32
-
33
-// Update replaces the given labels for a digest,
34
-// a key with an empty value removes a label.
35
-func (s *InMemory) Update(dgst digest.Digest, update map[string]string) (map[string]string, error) {
36
-	s.mu.Lock()
37
-	defer s.mu.Unlock()
38
-
39
-	labels, ok := s.labels[dgst]
40
-	if !ok {
41
-		labels = map[string]string{}
42
-	}
43
-	for k, v := range update {
44
-		labels[k] = v
45
-	}
46
-	if s.labels == nil {
47
-		s.labels = map[digest.Digest]map[string]string{}
48
-	}
49
-	s.labels[dgst] = labels
50
-
51
-	return labels, nil
52
-}
53 1
deleted file mode 100644
... ...
@@ -1,10 +0,0 @@
1
-package testutils
2
-
3
-import "testing"
4
-
5
-// Logger is used to log non-fatal messages during tests.
6
-type Logger interface {
7
-	Logf(format string, args ...any)
8
-}
9
-
10
-var _ Logger = (*testing.T)(nil)
11 1
deleted file mode 100644
... ...
@@ -1,200 +0,0 @@
1
-//go:build linux || freebsd
2
-
3
-package netnsutils
4
-
5
-import (
6
-	"fmt"
7
-	"runtime"
8
-	"strconv"
9
-	"testing"
10
-
11
-	"github.com/moby/moby/v2/daemon/libnetwork/ns"
12
-	"github.com/moby/moby/v2/internal/testutils"
13
-	"github.com/pkg/errors"
14
-	"github.com/vishvananda/netns"
15
-	"golang.org/x/sys/unix"
16
-)
17
-
18
-// OSContext is a handle to a test OS context.
19
-type OSContext struct {
20
-	origNS, newNS netns.NsHandle
21
-
22
-	tid    int
23
-	caller string // The file:line where SetupTestOSContextEx was called, for interpolating into error messages.
24
-}
25
-
26
-// SetupTestOSContext joins the current goroutine to a new network namespace,
27
-// and returns its associated teardown function.
28
-//
29
-// Example usage:
30
-//
31
-//	defer SetupTestOSContext(t)()
32
-func SetupTestOSContext(t *testing.T) func() {
33
-	c := SetupTestOSContextEx(t)
34
-	return func() { c.Cleanup(t) }
35
-}
36
-
37
-// SetupTestOSContextEx joins the current goroutine to a new network namespace.
38
-//
39
-// Compared to [SetupTestOSContext], this function allows goroutines to be
40
-// spawned which are associated with the same OS context via the returned
41
-// OSContext value.
42
-//
43
-// Example usage:
44
-//
45
-//	c := SetupTestOSContext(t)
46
-//	defer c.Cleanup(t)
47
-func SetupTestOSContextEx(t *testing.T) *OSContext {
48
-	runtime.LockOSThread()
49
-	origNS, err := netns.Get()
50
-	if err != nil {
51
-		runtime.UnlockOSThread()
52
-		t.Fatalf("Failed to open initial netns: %v", err)
53
-	}
54
-
55
-	c := OSContext{
56
-		tid:    unix.Gettid(),
57
-		origNS: origNS,
58
-	}
59
-	c.newNS, err = netns.New()
60
-	if err != nil {
61
-		// netns.New() is not atomic: it could have encountered an error
62
-		// after unsharing the current thread's network namespace.
63
-		c.restore(t)
64
-		t.Fatalf("Failed to enter netns: %v", err)
65
-	}
66
-
67
-	// Since we are switching to a new test namespace make
68
-	// sure to re-initialize initNs context
69
-	ns.Init()
70
-
71
-	nl := ns.NlHandle()
72
-	lo, err := nl.LinkByName("lo")
73
-	if err != nil {
74
-		c.restore(t)
75
-		t.Fatalf("Failed to get handle to loopback interface 'lo' in new netns: %v", err)
76
-	}
77
-	if err := nl.LinkSetUp(lo); err != nil {
78
-		c.restore(t)
79
-		t.Fatalf("Failed to enable loopback interface in new netns: %v", err)
80
-	}
81
-
82
-	_, file, line, ok := runtime.Caller(0)
83
-	if ok {
84
-		c.caller = file + ":" + strconv.Itoa(line)
85
-	}
86
-
87
-	return &c
88
-}
89
-
90
-// Cleanup tears down the OS context. It must be called from the same goroutine
91
-// as the [SetupTestOSContextEx] call which returned c.
92
-//
93
-// Explicit cleanup is required as (*testing.T).Cleanup() makes no guarantees
94
-// about which goroutine the cleanup functions are invoked on.
95
-func (c *OSContext) Cleanup(t *testing.T) {
96
-	t.Helper()
97
-	if unix.Gettid() != c.tid {
98
-		t.Fatalf("c.Cleanup() must be called from the same goroutine as SetupTestOSContextEx() (%s)", c.caller)
99
-	}
100
-	if err := c.newNS.Close(); err != nil {
101
-		t.Logf("Warning: netns closing failed (%v)", err)
102
-	}
103
-	c.restore(t)
104
-	ns.Init()
105
-}
106
-
107
-func (c *OSContext) restore(t *testing.T) {
108
-	t.Helper()
109
-	if err := netns.Set(c.origNS); err != nil {
110
-		t.Logf("Warning: failed to restore thread netns (%v)", err)
111
-	} else {
112
-		runtime.UnlockOSThread()
113
-	}
114
-
115
-	if err := c.origNS.Close(); err != nil {
116
-		t.Logf("Warning: netns closing failed (%v)", err)
117
-	}
118
-}
119
-
120
-// Set sets the OS context of the calling goroutine to c and returns a teardown
121
-// function to restore the calling goroutine's OS context and release resources.
122
-// The teardown function accepts an optional Logger argument.
123
-//
124
-// This is a lower-level interface which is less ergonomic than c.Go() but more
125
-// composable with other goroutine-spawning utilities such as [sync.WaitGroup]
126
-// or [golang.org/x/sync/errgroup.Group].
127
-//
128
-// Example usage:
129
-//
130
-//	func TestFoo(t *testing.T) {
131
-//		osctx := testutils.SetupTestOSContextEx(t)
132
-//		defer osctx.Cleanup(t)
133
-//		var eg errgroup.Group
134
-//		eg.Go(func() error {
135
-//			teardown, err := osctx.Set()
136
-//			if err != nil {
137
-//				return err
138
-//			}
139
-//			defer teardown(t)
140
-//			// ...
141
-//		})
142
-//		if err := eg.Wait(); err != nil {
143
-//			t.Fatalf("%+v", err)
144
-//		}
145
-//	}
146
-func (c *OSContext) Set() (func(testutils.Logger), error) {
147
-	runtime.LockOSThread()
148
-	orig, err := netns.Get()
149
-	if err != nil {
150
-		runtime.UnlockOSThread()
151
-		return nil, errors.Wrap(err, "failed to open initial netns for goroutine")
152
-	}
153
-	if err := errors.WithStack(netns.Set(c.newNS)); err != nil {
154
-		runtime.UnlockOSThread()
155
-		return nil, errors.Wrap(err, "failed to set goroutine network namespace")
156
-	}
157
-
158
-	tid := unix.Gettid()
159
-	_, file, line, callerOK := runtime.Caller(0)
160
-
161
-	return func(log testutils.Logger) {
162
-		if unix.Gettid() != tid {
163
-			msg := "teardown function must be called from the same goroutine as c.Set()"
164
-			if callerOK {
165
-				msg += fmt.Sprintf(" (%s:%d)", file, line)
166
-			}
167
-			panic(msg)
168
-		}
169
-
170
-		if err := netns.Set(orig); err != nil && log != nil {
171
-			log.Logf("Warning: failed to restore goroutine thread netns (%v)", err)
172
-		} else {
173
-			runtime.UnlockOSThread()
174
-		}
175
-
176
-		if err := orig.Close(); err != nil && log != nil {
177
-			log.Logf("Warning: netns closing failed (%v)", err)
178
-		}
179
-	}, nil
180
-}
181
-
182
-// Go starts running fn in a new goroutine inside the test OS context.
183
-func (c *OSContext) Go(t *testing.T, fn func()) {
184
-	t.Helper()
185
-	errCh := make(chan error, 1)
186
-	go func() {
187
-		teardown, err := c.Set()
188
-		if err != nil {
189
-			errCh <- err
190
-			return
191
-		}
192
-		defer teardown(t)
193
-		close(errCh)
194
-		fn()
195
-	}()
196
-
197
-	if err := <-errCh; err != nil {
198
-		t.Fatalf("%+v", err)
199
-	}
200
-}
201 1
deleted file mode 100644
... ...
@@ -1,8 +0,0 @@
1
-package netnsutils
2
-
3
-import "testing"
4
-
5
-// SetupTestOSContext is a no-op on Windows.
6
-func SetupTestOSContext(*testing.T) func() {
7
-	return func() {}
8
-}
9 1
deleted file mode 100644
... ...
@@ -1,43 +0,0 @@
1
-package netnsutils
2
-
3
-import (
4
-	"errors"
5
-	"syscall"
6
-	"testing"
7
-
8
-	"github.com/vishvananda/netns"
9
-	"golang.org/x/sys/unix"
10
-	"gotest.tools/v3/assert"
11
-)
12
-
13
-// AssertSocketSameNetNS makes a best-effort attempt to assert that conn is in
14
-// the same network namespace as the current goroutine's thread.
15
-func AssertSocketSameNetNS(t testing.TB, conn syscall.Conn) {
16
-	t.Helper()
17
-
18
-	sc, err := conn.SyscallConn()
19
-	assert.NilError(t, err)
20
-	sc.Control(func(fd uintptr) {
21
-		srvnsfd, err := unix.IoctlRetInt(int(fd), unix.SIOCGSKNS)
22
-		if err != nil {
23
-			if errors.Is(err, unix.EPERM) {
24
-				t.Log("Cannot determine socket's network namespace. Do we have CAP_NET_ADMIN?")
25
-				return
26
-			}
27
-			if errors.Is(err, unix.ENOSYS) {
28
-				t.Log("Cannot query socket's network namespace due to missing kernel support.")
29
-				return
30
-			}
31
-			t.Fatal(err)
32
-		}
33
-		srvns := netns.NsHandle(srvnsfd)
34
-		defer srvns.Close()
35
-
36
-		curns, err := netns.Get()
37
-		assert.NilError(t, err)
38
-		defer curns.Close()
39
-		if !srvns.Equal(curns) {
40
-			t.Fatalf("Socket is in network namespace %s, but test goroutine is in %s", srvns, curns)
41
-		}
42
-	})
43
-}
44 1
deleted file mode 100644
... ...
@@ -1,11 +0,0 @@
1
-//go:build !linux
2
-
3
-package netnsutils
4
-
5
-import (
6
-	"syscall"
7
-	"testing"
8
-)
9
-
10
-// AssertSocketSameNetNS is a no-op on platforms other than Linux.
11
-func AssertSocketSameNetNS(t testing.TB, conn syscall.Conn) {}
12 1
deleted file mode 100644
... ...
@@ -1,33 +0,0 @@
1
-package specialimage
2
-
3
-import (
4
-	"github.com/containerd/platforms"
5
-	"github.com/distribution/reference"
6
-	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
7
-)
8
-
9
-// ConfigTarget creates an image index with an image config being used as an
10
-// image target instead of a manifest or index.
11
-func ConfigTarget(dir string) (*ocispec.Index, error) {
12
-	const imageRef = "config:latest"
13
-
14
-	ref, err := reference.ParseNormalizedNamed(imageRef)
15
-	if err != nil {
16
-		return nil, err
17
-	}
18
-
19
-	desc, err := writeJsonBlob(dir, ocispec.MediaTypeImageConfig, ocispec.Image{
20
-		Platform: platforms.MustParse("linux/amd64"),
21
-		Config: ocispec.ImageConfig{
22
-			Env: []string{"FOO=BAR"},
23
-		},
24
-	})
25
-	if err != nil {
26
-		return nil, err
27
-	}
28
-	desc.Annotations = map[string]string{
29
-		"io.containerd.image.name": ref.String(),
30
-	}
31
-
32
-	return ociImage(dir, ref, desc)
33
-}
34 1
deleted file mode 100644
... ...
@@ -1,45 +0,0 @@
1
-package specialimage
2
-
3
-import (
4
-	"os"
5
-	"path/filepath"
6
-	"strings"
7
-
8
-	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
9
-)
10
-
11
-const (
12
-	danglingImageManifestDigest = "sha256:16d365089e5c10e1673ee82ab5bba38ade9b763296ad918bd24b42a1156c5456" // #nosec G101 -- ignoring: Potential hardcoded credentials (gosec)
13
-	danglingImageConfigDigest   = "sha256:0df1207206e5288f4a989a2f13d1f5b3c4e70467702c1d5d21dfc9f002b7bd43" // #nosec G101 -- ignoring: Potential hardcoded credentials (gosec)
14
-)
15
-
16
-// Dangling creates an image with no layers and no tag.
17
-// It also has an extra org.mobyproject.test.specialimage=1 label set.
18
-// Layout: OCI.
19
-func Dangling(dir string) (*ocispec.Index, error) {
20
-	if err := os.WriteFile(filepath.Join(dir, "index.json"), []byte(`{"schemaVersion":2,"manifests":[{"mediaType":"application/vnd.docker.distribution.manifest.v2+json","digest":"sha256:16d365089e5c10e1673ee82ab5bba38ade9b763296ad918bd24b42a1156c5456","size":264,"annotations":{"org.opencontainers.image.created":"2023-05-19T08:00:44Z"},"platform":{"architecture":"amd64","os":"linux"}}]}`), 0o644); err != nil {
21
-		return nil, err
22
-	}
23
-
24
-	if err := os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(`[{"Config":"blobs/sha256/0df1207206e5288f4a989a2f13d1f5b3c4e70467702c1d5d21dfc9f002b7bd43","RepoTags":null,"Layers":null}]`), 0o644); err != nil {
25
-		return nil, err
26
-	}
27
-
28
-	if err := os.Mkdir(filepath.Join(dir, "blobs"), 0o755); err != nil {
29
-		return nil, err
30
-	}
31
-
32
-	blobsDir := filepath.Join(dir, "blobs", "sha256")
33
-	if err := os.Mkdir(blobsDir, 0o755); err != nil {
34
-		return nil, err
35
-	}
36
-
37
-	if err := os.WriteFile(filepath.Join(blobsDir, strings.TrimPrefix(danglingImageManifestDigest, "sha256:")), []byte(`{"schemaVersion":2,"mediaType":"application/vnd.docker.distribution.manifest.v2+json","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:0df1207206e5288f4a989a2f13d1f5b3c4e70467702c1d5d21dfc9f002b7bd43","size":390},"layers":[]}`), 0o644); err != nil {
38
-		return nil, err
39
-	}
40
-	if err := os.WriteFile(filepath.Join(blobsDir, strings.TrimPrefix(danglingImageConfigDigest, "sha256:")), []byte(`{"architecture":"amd64","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"WorkingDir":"/","Labels":{"org.mobyproject.test.specialimage":"1"},"OnBuild":null},"created":null,"history":[{"created_by":"LABEL org.mobyproject.test.specialimage=1","comment":"buildkit.dockerfile.v0","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":null}}`), 0o644); err != nil {
41
-		return nil, err
42
-	}
43
-
44
-	return nil, nil
45
-}
46 1
deleted file mode 100644
... ...
@@ -1,66 +0,0 @@
1
-package specialimage
2
-
3
-import (
4
-	"io"
5
-	"os"
6
-	"path/filepath"
7
-
8
-	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
9
-)
10
-
11
-// EmptyFS builds an image with an empty rootfs.
12
-// Layout: Legacy Docker Archive
13
-// See https://github.com/moby/moby/pull/5262
14
-// and also https://github.com/moby/moby/issues/4242
15
-func EmptyFS(dir string) (*ocispec.Index, error) {
16
-	if err := os.WriteFile(filepath.Join(dir, "manifest.json"), []byte(`[{"Config":"11f64303f0f7ffdc71f001788132bca5346831939a956e3e975c93267d89a16d.json","RepoTags":["emptyfs:latest"],"Layers":["511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158/layer.tar"]}]`), 0o644); err != nil {
17
-		return nil, err
18
-	}
19
-
20
-	if err := os.Mkdir(filepath.Join(dir, "blobs"), 0o755); err != nil {
21
-		return nil, err
22
-	}
23
-
24
-	blobsDir := filepath.Join(dir, "511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158")
25
-	if err := os.Mkdir(blobsDir, 0o755); err != nil {
26
-		return nil, err
27
-	}
28
-
29
-	if err := os.WriteFile(filepath.Join(dir, "VERSION"), []byte(`1.0`), 0o644); err != nil {
30
-		return nil, err
31
-	}
32
-	if err := os.WriteFile(filepath.Join(dir, "repositories"), []byte(`{"emptyfs":{"latest":"511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158"}}`), 0o644); err != nil {
33
-		return nil, err
34
-	}
35
-	if err := os.WriteFile(filepath.Join(dir, "11f64303f0f7ffdc71f001788132bca5346831939a956e3e975c93267d89a16d.json"), []byte(`{"architecture":"x86_64","comment":"Imported from -","container_config":{"Hostname":"","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":null,"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"OnBuild":null,"Labels":null},"created":"2013-06-13T14:03:50.821769-07:00","docker_version":"0.4.0","history":[{"created":"2013-06-13T14:03:50.821769-07:00","comment":"Imported from -"}],"rootfs":{"type":"layers","diff_ids":["sha256:84ff92691f909a05b224e1c56abb4864f01b4f8e3c854e4bb4c7baf1d3f6d652"]}}`), 0o644); err != nil {
36
-		return nil, err
37
-	}
38
-
39
-	if err := os.WriteFile(filepath.Join(blobsDir, "json"), []byte(`{"id":"511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158","comment":"Imported from -","created":"2013-06-13T14:03:50.821769-07:00","container_config":{"Hostname":"","Domainname":"","User":"","Memory":0,"MemorySwap":0,"CpuShares":0,"AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"PortSpecs":null,"ExposedPorts":null,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":null,"Image":"","Volumes":null,"WorkingDir":"","Entrypoint":null,"NetworkDisabled":false,"OnBuild":null},"docker_version":"0.4.0","architecture":"x86_64","Size":0}`+"\n"), 0o644); err != nil {
40
-		return nil, err
41
-	}
42
-
43
-	layerFile, err := os.OpenFile(filepath.Join(blobsDir, "layer.tar"), os.O_CREATE|os.O_WRONLY, 0o644)
44
-	if err != nil {
45
-		return nil, err
46
-	}
47
-	defer layerFile.Close()
48
-
49
-	// 10240 NUL bytes is a valid empty tar archive.
50
-	_, err = io.Copy(layerFile, io.LimitReader(zeroReader{}, 10240))
51
-	if err != nil {
52
-		return nil, err
53
-	}
54
-
55
-	return nil, nil
56
-}
57
-
58
-type zeroReader struct{}
59
-
60
-func (zeroReader) Read(p []byte) (int, error) {
61
-	l := len(p)
62
-	for idx := 0; idx < l; idx++ {
63
-		p[idx] = 0
64
-	}
65
-	return l, nil
66
-}
67 1
deleted file mode 100644
... ...
@@ -1,25 +0,0 @@
1
-package specialimage
2
-
3
-import (
4
-	"github.com/distribution/reference"
5
-	"github.com/opencontainers/image-spec/specs-go"
6
-	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
7
-)
8
-
9
-// EmptyIndex creates an image index with no manifests.
10
-// This is equivalent to `tianon/scratch:index`.
11
-func EmptyIndex(dir string) (*ocispec.Index, error) {
12
-	const imageRef = "emptyindex:latest"
13
-
14
-	index := ocispec.Index{
15
-		Versioned: specs.Versioned{SchemaVersion: 2},
16
-		MediaType: ocispec.MediaTypeImageIndex,
17
-		Manifests: []ocispec.Descriptor{},
18
-	}
19
-
20
-	ref, err := reference.ParseNormalizedNamed(imageRef)
21
-	if err != nil {
22
-		return nil, err
23
-	}
24
-	return multiPlatformImage(dir, ref, index)
25
-}
26 1
deleted file mode 100644
... ...
@@ -1,7 +0,0 @@
1
-package specialimage
2
-
3
-import (
4
-	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
5
-)
6
-
7
-type SpecialImageFunc func(string) (*ocispec.Index, error)
8 1
deleted file mode 100644
... ...
@@ -1,218 +0,0 @@
1
-package specialimage
2
-
3
-import (
4
-	"bytes"
5
-	"encoding/json"
6
-	"io"
7
-	"os"
8
-	"path/filepath"
9
-
10
-	"github.com/containerd/platforms"
11
-	"github.com/distribution/reference"
12
-	"github.com/google/uuid"
13
-	"github.com/moby/go-archive"
14
-	"github.com/opencontainers/go-digest"
15
-	"github.com/opencontainers/image-spec/specs-go"
16
-	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
17
-)
18
-
19
-type SingleFileLayer struct {
20
-	Name    string
21
-	Content []byte
22
-}
23
-
24
-func MultiLayer(dir string) (*ocispec.Index, error) {
25
-	return MultiLayerCustom(dir, "multilayer:latest", []SingleFileLayer{
26
-		{Name: "foo", Content: []byte("1")},
27
-		{Name: "bar", Content: []byte("2")},
28
-		{Name: "hello", Content: []byte("world")},
29
-	})
30
-}
31
-
32
-func MultiLayerCustom(dir string, imageRef string, layers []SingleFileLayer) (*ocispec.Index, error) {
33
-	var layerDescs []ocispec.Descriptor
34
-	var layerDgsts []digest.Digest
35
-	var layerBlobs []string
36
-	for _, layer := range layers {
37
-		layerDesc, err := writeLayerWithOneFile(dir, layer.Name, layer.Content)
38
-		if err != nil {
39
-			return nil, err
40
-		}
41
-
42
-		layerDescs = append(layerDescs, layerDesc)
43
-		layerDgsts = append(layerDgsts, layerDesc.Digest)
44
-		layerBlobs = append(layerBlobs, blobPath(layerDesc))
45
-	}
46
-
47
-	configDesc, err := writeJsonBlob(dir, ocispec.MediaTypeImageConfig, ocispec.Image{
48
-		Platform: platforms.DefaultSpec(),
49
-		Config: ocispec.ImageConfig{
50
-			Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
51
-		},
52
-		RootFS: ocispec.RootFS{
53
-			Type:    "layers",
54
-			DiffIDs: layerDgsts,
55
-		},
56
-	})
57
-	if err != nil {
58
-		return nil, err
59
-	}
60
-
61
-	manifest := ocispec.Manifest{
62
-		MediaType: ocispec.MediaTypeImageManifest,
63
-		Config:    configDesc,
64
-		Layers:    layerDescs,
65
-	}
66
-
67
-	legacyManifests := []manifestItem{
68
-		{
69
-			Config:   blobPath(configDesc),
70
-			RepoTags: []string{imageRef},
71
-			Layers:   layerBlobs,
72
-		},
73
-	}
74
-
75
-	ref, err := reference.ParseNormalizedNamed(imageRef)
76
-	if err != nil {
77
-		return nil, err
78
-	}
79
-	return singlePlatformImage(dir, ref, manifest, legacyManifests)
80
-}
81
-
82
-// Legacy manifest item (manifests.json)
83
-type manifestItem struct {
84
-	Config   string
85
-	RepoTags []string
86
-	Layers   []string
87
-}
88
-
89
-func singlePlatformImage(dir string, ref reference.Named, manifest ocispec.Manifest, legacyManifests []manifestItem) (*ocispec.Index, error) {
90
-	manifestDesc, err := writeJsonBlob(dir, ocispec.MediaTypeImageManifest, manifest)
91
-	if err != nil {
92
-		return nil, err
93
-	}
94
-
95
-	if ref != nil {
96
-		manifestDesc.Annotations = map[string]string{
97
-			"io.containerd.image.name": ref.String(),
98
-		}
99
-
100
-		if tagged, ok := ref.(reference.Tagged); ok {
101
-			manifestDesc.Annotations[ocispec.AnnotationRefName] = tagged.Tag()
102
-		}
103
-	}
104
-
105
-	if err := writeJson(legacyManifests, filepath.Join(dir, "manifest.json")); err != nil {
106
-		return nil, err
107
-	}
108
-
109
-	return ociImage(dir, ref, manifestDesc)
110
-}
111
-
112
-func ociImage(dir string, ref reference.Named, target ocispec.Descriptor) (*ocispec.Index, error) {
113
-	idx := ocispec.Index{
114
-		Versioned: specs.Versioned{SchemaVersion: 2},
115
-		MediaType: ocispec.MediaTypeImageIndex,
116
-		Manifests: []ocispec.Descriptor{target},
117
-	}
118
-	if err := writeJson(idx, filepath.Join(dir, "index.json")); err != nil {
119
-		return nil, err
120
-	}
121
-
122
-	err := os.WriteFile(filepath.Join(dir, "oci-layout"), []byte(`{"imageLayoutVersion": "1.0.0"}`), 0o644)
123
-	if err != nil {
124
-		return nil, err
125
-	}
126
-
127
-	return &idx, nil
128
-}
129
-
130
-func fileArchive(dir string, name string, content []byte) (io.ReadCloser, error) {
131
-	tmp, err := os.MkdirTemp("", "")
132
-	if err != nil {
133
-		return nil, err
134
-	}
135
-
136
-	if err := os.WriteFile(filepath.Join(tmp, name), content, 0o644); err != nil {
137
-		return nil, err
138
-	}
139
-
140
-	return archive.Tar(tmp, archive.Uncompressed)
141
-}
142
-
143
-func writeLayerWithOneFile(dir string, filename string, content []byte) (ocispec.Descriptor, error) {
144
-	rd, err := fileArchive(dir, filename, content)
145
-	if err != nil {
146
-		return ocispec.Descriptor{}, err
147
-	}
148
-	defer rd.Close()
149
-
150
-	return writeBlob(dir, ocispec.MediaTypeImageLayer, rd)
151
-}
152
-
153
-func writeJsonBlob(dir string, mt string, obj any) (ocispec.Descriptor, error) {
154
-	b, err := json.Marshal(obj)
155
-	if err != nil {
156
-		return ocispec.Descriptor{}, err
157
-	}
158
-
159
-	return writeBlob(dir, mt, bytes.NewReader(b))
160
-}
161
-
162
-func writeJson(obj any, path string) error {
163
-	b, err := json.Marshal(obj)
164
-	if err != nil {
165
-		return err
166
-	}
167
-
168
-	return os.WriteFile(path, b, 0o644)
169
-}
170
-
171
-func writeBlob(dir string, mt string, rd io.Reader) (_ ocispec.Descriptor, outErr error) {
172
-	digester := digest.Canonical.Digester()
173
-	hashTee := io.TeeReader(rd, digester.Hash())
174
-
175
-	blobsPath := filepath.Join(dir, "blobs", "sha256")
176
-	if err := os.MkdirAll(blobsPath, 0o755); err != nil {
177
-		return ocispec.Descriptor{}, err
178
-	}
179
-
180
-	tmpPath := filepath.Join(blobsPath, uuid.New().String())
181
-	file, err := os.Create(tmpPath)
182
-	if err != nil {
183
-		return ocispec.Descriptor{}, err
184
-	}
185
-
186
-	defer func() {
187
-		if outErr != nil {
188
-			file.Close()
189
-			os.Remove(tmpPath)
190
-		}
191
-	}()
192
-
193
-	if _, err := io.Copy(file, hashTee); err != nil {
194
-		return ocispec.Descriptor{}, err
195
-	}
196
-
197
-	digest := digester.Digest()
198
-
199
-	stat, err := os.Stat(tmpPath)
200
-	if err != nil {
201
-		return ocispec.Descriptor{}, err
202
-	}
203
-
204
-	file.Close()
205
-	if err := os.Rename(tmpPath, filepath.Join(blobsPath, digest.Encoded())); err != nil {
206
-		return ocispec.Descriptor{}, err
207
-	}
208
-
209
-	return ocispec.Descriptor{
210
-		MediaType: mt,
211
-		Digest:    digest,
212
-		Size:      stat.Size(),
213
-	}, nil
214
-}
215
-
216
-func blobPath(desc ocispec.Descriptor) string {
217
-	return "blobs/sha256/" + desc.Digest.Encoded()
218
-}
219 1
deleted file mode 100644
... ...
@@ -1,33 +0,0 @@
1
-package specialimage
2
-
3
-import (
4
-	"github.com/containerd/platforms"
5
-	"github.com/distribution/reference"
6
-	"github.com/opencontainers/image-spec/specs-go"
7
-	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
8
-)
9
-
10
-func MultiPlatform(dir string, imageRef string, imagePlatforms []ocispec.Platform) (*ocispec.Index, []ocispec.Descriptor, error) {
11
-	ref, err := reference.ParseNormalizedNamed(imageRef)
12
-	if err != nil {
13
-		return nil, nil, err
14
-	}
15
-
16
-	var descs []ocispec.Descriptor
17
-
18
-	for _, platform := range imagePlatforms {
19
-		ps := platforms.FormatAll(platform)
20
-		manifestDesc, _, err := oneLayerPlatformManifest(dir, platform, FileInLayer{Path: "bash", Content: []byte("layer-" + ps)})
21
-		if err != nil {
22
-			return nil, nil, err
23
-		}
24
-		descs = append(descs, manifestDesc)
25
-	}
26
-
27
-	idx, err := multiPlatformImage(dir, ref, ocispec.Index{
28
-		Versioned: specs.Versioned{SchemaVersion: 2},
29
-		MediaType: ocispec.MediaTypeImageIndex,
30
-		Manifests: descs,
31
-	})
32
-	return idx, descs, err
33
-}
34 1
deleted file mode 100644
... ...
@@ -1,55 +0,0 @@
1
-package specialimage
2
-
3
-import (
4
-	"github.com/containerd/platforms"
5
-	"github.com/distribution/reference"
6
-	"github.com/opencontainers/go-digest"
7
-	"github.com/opencontainers/image-spec/specs-go"
8
-	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
9
-)
10
-
11
-type PartialOpts struct {
12
-	Stored  []ocispec.Platform
13
-	Missing []ocispec.Platform
14
-}
15
-
16
-// PartialMultiPlatform creates an index with all platforms in storedPlatforms
17
-// and missingPlatforms. However, only the blobs of the storedPlatforms are
18
-// created and stored, while the missingPlatforms are only referenced in the
19
-// index.
20
-func PartialMultiPlatform(dir string, imageRef string, opts PartialOpts) (*ocispec.Index, []ocispec.Descriptor, error) {
21
-	ref, err := reference.ParseNormalizedNamed(imageRef)
22
-	if err != nil {
23
-		return nil, nil, err
24
-	}
25
-
26
-	var descs []ocispec.Descriptor
27
-
28
-	for _, platform := range opts.Stored {
29
-		ps := platforms.FormatAll(platform)
30
-		manifestDesc, _, err := oneLayerPlatformManifest(dir, platform, FileInLayer{Path: "bash", Content: []byte("layer-" + ps)})
31
-		if err != nil {
32
-			return nil, nil, err
33
-		}
34
-		descs = append(descs, manifestDesc)
35
-	}
36
-
37
-	for _, platform := range opts.Missing {
38
-		platformStr := platforms.FormatAll(platform)
39
-		dgst := digest.FromBytes([]byte(platformStr))
40
-
41
-		descs = append(descs, ocispec.Descriptor{
42
-			MediaType: ocispec.MediaTypeImageManifest,
43
-			Size:      128,
44
-			Platform:  &platform,
45
-			Digest:    dgst,
46
-		})
47
-	}
48
-
49
-	idx, err := multiPlatformImage(dir, ref, ocispec.Index{
50
-		Versioned: specs.Versioned{SchemaVersion: 2},
51
-		MediaType: ocispec.MediaTypeImageIndex,
52
-		Manifests: descs,
53
-	})
54
-	return idx, descs, err
55
-}
56 1
deleted file mode 100644
... ...
@@ -1,109 +0,0 @@
1
-package specialimage
2
-
3
-import (
4
-	"encoding/json"
5
-	"math/rand"
6
-	"os"
7
-	"path/filepath"
8
-	"strconv"
9
-
10
-	"github.com/distribution/reference"
11
-	"github.com/opencontainers/go-digest"
12
-	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
13
-)
14
-
15
-func RandomSinglePlatform(dir string, platform ocispec.Platform, source rand.Source) (*ocispec.Index, error) {
16
-	r := rand.New(source) //nolint:gosec // Ignore G404: Use of weak random number generator (math/rand instead of crypto/rand)
17
-
18
-	imageRef := "random-" + strconv.FormatInt(r.Int63(), 10) + ":latest"
19
-
20
-	layerCount := r.Intn(8)
21
-
22
-	var layers []ocispec.Descriptor
23
-	for i := 0; i < layerCount; i++ {
24
-		layerDesc, err := writeLayerWithOneFile(dir, "layer-"+strconv.Itoa(i), []byte(strconv.Itoa(i)))
25
-		if err != nil {
26
-			return nil, err
27
-		}
28
-		layers = append(layers, layerDesc)
29
-	}
30
-
31
-	configDesc, err := writeJsonBlob(dir, ocispec.MediaTypeImageConfig, ocispec.Image{
32
-		Platform: platform,
33
-		Config: ocispec.ImageConfig{
34
-			Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
35
-		},
36
-		RootFS: ocispec.RootFS{
37
-			Type:    "layers",
38
-			DiffIDs: layersToDigests(layers),
39
-		},
40
-	})
41
-	if err != nil {
42
-		return nil, err
43
-	}
44
-
45
-	manifest := ocispec.Manifest{
46
-		MediaType: ocispec.MediaTypeImageManifest,
47
-		Config:    configDesc,
48
-		Layers:    layers,
49
-	}
50
-
51
-	legacyManifests := []manifestItem{
52
-		{
53
-			Config:   blobPath(configDesc),
54
-			RepoTags: []string{imageRef},
55
-			Layers:   blobPaths(layers),
56
-		},
57
-	}
58
-
59
-	ref, err := reference.ParseNormalizedNamed(imageRef)
60
-	if err != nil {
61
-		return nil, err
62
-	}
63
-	return singlePlatformImage(dir, ref, manifest, legacyManifests)
64
-}
65
-
66
-func layersToDigests(layers []ocispec.Descriptor) []digest.Digest {
67
-	var digests []digest.Digest
68
-	for _, l := range layers {
69
-		digests = append(digests, l.Digest)
70
-	}
71
-	return digests
72
-}
73
-
74
-func blobPaths(descriptors []ocispec.Descriptor) []string {
75
-	var paths []string
76
-	for _, d := range descriptors {
77
-		paths = append(paths, blobPath(d))
78
-	}
79
-	return paths
80
-}
81
-
82
-func readJson(path string, v any) error {
83
-	content, err := os.ReadFile(path)
84
-	if err != nil {
85
-		return err
86
-	}
87
-	return json.Unmarshal(content, v)
88
-}
89
-
90
-func LegacyManifest(dir string, imageRef string, mfstDesc ocispec.Descriptor) error {
91
-	legacyManifests := []manifestItem{}
92
-
93
-	var mfst ocispec.Manifest
94
-	if err := readJson(filepath.Join(dir, blobPath(mfstDesc)), &mfst); err != nil {
95
-		return err
96
-	}
97
-
98
-	legacyManifests = append(legacyManifests, manifestItem{
99
-		Config:   blobPath(mfst.Config),
100
-		RepoTags: []string{imageRef},
101
-		Layers:   blobPaths(mfst.Layers),
102
-	})
103
-
104
-	if err := writeJson(legacyManifests, filepath.Join(dir, "manifest.json")); err != nil {
105
-		return err
106
-	}
107
-
108
-	return nil
109
-}
110 1
deleted file mode 100644
... ...
@@ -1,39 +0,0 @@
1
-package specialimage
2
-
3
-import (
4
-	"strings"
5
-
6
-	"github.com/distribution/reference"
7
-	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
8
-)
9
-
10
-// TextPlain creates an non-container image that only contains a text/plain blob.
11
-func TextPlain(dir string) (*ocispec.Index, error) {
12
-	ref, err := reference.ParseNormalizedNamed("tianon/test:text-plain")
13
-	if err != nil {
14
-		return nil, err
15
-	}
16
-
17
-	emptyJsonDesc, err := writeBlob(dir, "text/plain", strings.NewReader("{}"))
18
-	if err != nil {
19
-		return nil, err
20
-	}
21
-
22
-	configDesc := emptyJsonDesc
23
-	configDesc.MediaType = "application/vnd.oci.empty.v1+json"
24
-
25
-	desc, err := writeJsonBlob(dir, ocispec.MediaTypeImageManifest, ocispec.Manifest{
26
-		Config: configDesc,
27
-		Layers: []ocispec.Descriptor{
28
-			emptyJsonDesc,
29
-		},
30
-	})
31
-	if err != nil {
32
-		return nil, err
33
-	}
34
-	desc.Annotations = map[string]string{
35
-		"io.containerd.image.name": ref.String(),
36
-	}
37
-
38
-	return ociImage(dir, nil, desc)
39
-}
40 1
deleted file mode 100644
... ...
@@ -1,113 +0,0 @@
1
-package specialimage
2
-
3
-import (
4
-	"os"
5
-	"path/filepath"
6
-
7
-	"github.com/containerd/platforms"
8
-	"github.com/distribution/reference"
9
-	"github.com/opencontainers/go-digest"
10
-	"github.com/opencontainers/image-spec/specs-go"
11
-	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
12
-)
13
-
14
-func TwoPlatform(dir string) (*ocispec.Index, error) {
15
-	const imageRef = "twoplatform:latest"
16
-	ref, err := reference.ParseNormalizedNamed(imageRef)
17
-	if err != nil {
18
-		return nil, err
19
-	}
20
-
21
-	manifest1Desc, _, err := oneLayerPlatformManifest(dir, platforms.MustParse("linux/amd64"), FileInLayer{Path: "bash", Content: []byte("layer1")})
22
-	if err != nil {
23
-		return nil, err
24
-	}
25
-
26
-	manifest2Desc, _, err := oneLayerPlatformManifest(dir, platforms.MustParse("linux/arm64"), FileInLayer{Path: "bash", Content: []byte("layer2")})
27
-	if err != nil {
28
-		return nil, err
29
-	}
30
-
31
-	return multiPlatformImage(dir, ref, ocispec.Index{
32
-		Versioned: specs.Versioned{SchemaVersion: 2},
33
-		MediaType: ocispec.MediaTypeImageIndex,
34
-		Manifests: []ocispec.Descriptor{manifest1Desc, manifest2Desc},
35
-	})
36
-}
37
-
38
-type FileInLayer struct {
39
-	Path    string
40
-	Content []byte
41
-}
42
-
43
-func oneLayerPlatformManifest(dir string, platform ocispec.Platform, f FileInLayer) (ocispec.Descriptor, manifestItem, error) {
44
-	layerDesc, err := writeLayerWithOneFile(dir, f.Path, f.Content)
45
-	if err != nil {
46
-		return ocispec.Descriptor{}, manifestItem{}, err
47
-	}
48
-
49
-	img := ocispec.Image{
50
-		Platform: platform,
51
-		Config: ocispec.ImageConfig{
52
-			Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
53
-		},
54
-		RootFS: ocispec.RootFS{
55
-			Type:    "layers",
56
-			DiffIDs: []digest.Digest{layerDesc.Digest},
57
-		},
58
-	}
59
-
60
-	configDesc, err := writeJsonBlob(dir, ocispec.MediaTypeImageConfig, img)
61
-	if err != nil {
62
-		return ocispec.Descriptor{}, manifestItem{}, err
63
-	}
64
-
65
-	manifestDesc, err := writeJsonBlob(dir, ocispec.MediaTypeImageManifest, ocispec.Manifest{
66
-		MediaType: ocispec.MediaTypeImageManifest,
67
-		Config:    configDesc,
68
-		Layers:    []ocispec.Descriptor{layerDesc},
69
-	})
70
-	if err != nil {
71
-		return ocispec.Descriptor{}, manifestItem{}, err
72
-	}
73
-	manifestDesc.Platform = &platform
74
-
75
-	return manifestDesc, manifestItem{
76
-		Config: blobPath(configDesc),
77
-		Layers: []string{blobPath(layerDesc)},
78
-	}, nil
79
-}
80
-
81
-func multiPlatformImage(dir string, ref reference.Named, target ocispec.Index) (*ocispec.Index, error) {
82
-	targetDesc, err := writeJsonBlob(dir, ocispec.MediaTypeImageIndex, target)
83
-	if err != nil {
84
-		return nil, err
85
-	}
86
-
87
-	if ref != nil {
88
-		targetDesc.Annotations = map[string]string{
89
-			"io.containerd.image.name": ref.String(),
90
-		}
91
-
92
-		if tagged, ok := ref.(reference.Tagged); ok {
93
-			targetDesc.Annotations[ocispec.AnnotationRefName] = tagged.Tag()
94
-		}
95
-	}
96
-
97
-	index := ocispec.Index{
98
-		Versioned: specs.Versioned{SchemaVersion: 2},
99
-		MediaType: ocispec.MediaTypeImageIndex,
100
-		Manifests: []ocispec.Descriptor{targetDesc},
101
-	}
102
-
103
-	if err := writeJson(index, filepath.Join(dir, "index.json")); err != nil {
104
-		return nil, err
105
-	}
106
-
107
-	err = os.WriteFile(filepath.Join(dir, "oci-layout"), []byte(`{"imageLayoutVersion": "1.0.0"}`), 0o644)
108
-	if err != nil {
109
-		return nil, err
110
-	}
111
-
112
-	return &index, nil
113
-}
114 1
deleted file mode 100644
... ...
@@ -1,20 +0,0 @@
1
-package storeutils
2
-
3
-import (
4
-	"testing"
5
-
6
-	"github.com/moby/moby/v2/daemon/libnetwork/datastore"
7
-	"gotest.tools/v3/assert"
8
-)
9
-
10
-// NewTempStore creates a new temporary libnetwork store for testing purposes.
11
-// The store is created in a temporary directory that is cleaned up when the
12
-// test finishes.
13
-func NewTempStore(t *testing.T) *datastore.Store {
14
-	t.Helper()
15
-
16
-	ds, err := datastore.New(t.TempDir(), "libnetwork")
17
-	assert.NilError(t, err)
18
-
19
-	return ds
20
-}