Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
| ... | ... |
@@ -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 |
) |
| ... | ... |
@@ -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" |
| ... | ... |
@@ -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 |
|
| ... | ... |
@@ -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" |
| ... | ... |
@@ -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 | 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 | 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 | 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 | 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 |
-} |
| 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 |
-} |
| 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 |
-} |
| 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 |
-} |
| 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 |
-} |