Browse code

c8d/save: Add tests

Test saving a shallow/partial image

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>

Paweł Gronowski authored on 2024/10/22 18:50:03
Showing 2 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,103 @@
0
+package containerd
1
+
2
+import (
3
+	"context"
4
+	"io"
5
+	"path/filepath"
6
+	"testing"
7
+
8
+	"github.com/containerd/containerd/namespaces"
9
+	"github.com/containerd/platforms"
10
+	"github.com/docker/docker/errdefs"
11
+	"github.com/docker/docker/internal/testutils/specialimage"
12
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
13
+	"gotest.tools/v3/assert"
14
+	is "gotest.tools/v3/assert/cmp"
15
+)
16
+
17
+func TestImageMultiplatformSaveShallowWithNative(t *testing.T) {
18
+	ctx := namespaces.WithNamespace(context.TODO(), "testing-"+t.Name())
19
+
20
+	contentDir := t.TempDir()
21
+	store := &blobsDirContentStore{blobs: filepath.Join(contentDir, "blobs/sha256")}
22
+
23
+	native := platforms.Platform{
24
+		OS:           "linux",
25
+		Architecture: "amd64",
26
+	}
27
+
28
+	arm64 := platforms.Platform{
29
+		OS:           "linux",
30
+		Architecture: "arm64",
31
+	}
32
+
33
+	imgSvc := fakeImageService(t, ctx, store)
34
+	// Mock the native platform.
35
+	imgSvc.defaultPlatformOverride = platforms.Only(native)
36
+
37
+	idx, _, err := specialimage.PartialMultiPlatform(contentDir, "partial-with-native:latest", specialimage.PartialOpts{
38
+		Stored:  []ocispec.Platform{native},
39
+		Missing: []ocispec.Platform{arm64},
40
+	})
41
+	assert.NilError(t, err)
42
+
43
+	img, err := imgSvc.images.Create(ctx, imagesFromIndex(idx)[0])
44
+	assert.NilError(t, err)
45
+
46
+	t.Run("export without specific platform", func(t *testing.T) {
47
+		err = imgSvc.ExportImage(ctx, []string{img.Name}, nil, io.Discard)
48
+		assert.NilError(t, err)
49
+	})
50
+	t.Run("export native", func(t *testing.T) {
51
+		err = imgSvc.ExportImage(ctx, []string{img.Name}, &native, io.Discard)
52
+		assert.NilError(t, err)
53
+	})
54
+	t.Run("export missing", func(t *testing.T) {
55
+		err = imgSvc.ExportImage(ctx, []string{img.Name}, &arm64, io.Discard)
56
+		assert.Check(t, is.ErrorType(err, errdefs.IsNotFound))
57
+	})
58
+}
59
+
60
+func TestImageMultiplatformSaveShallowWithoutNative(t *testing.T) {
61
+	ctx := namespaces.WithNamespace(context.TODO(), "testing-"+t.Name())
62
+
63
+	contentDir := t.TempDir()
64
+	store := &blobsDirContentStore{blobs: filepath.Join(contentDir, "blobs/sha256")}
65
+
66
+	native := platforms.Platform{
67
+		OS:           "linux",
68
+		Architecture: "amd64",
69
+	}
70
+
71
+	arm64 := platforms.Platform{
72
+		OS:           "linux",
73
+		Architecture: "arm64",
74
+	}
75
+
76
+	imgSvc := fakeImageService(t, ctx, store)
77
+	// Mock the native platform.
78
+	imgSvc.defaultPlatformOverride = platforms.Only(native)
79
+
80
+	idx, _, err := specialimage.PartialMultiPlatform(contentDir, "partial-without-native:latest", specialimage.PartialOpts{
81
+		Stored:  []ocispec.Platform{arm64},
82
+		Missing: []ocispec.Platform{native},
83
+	})
84
+	assert.NilError(t, err)
85
+
86
+	img, err := imgSvc.images.Create(ctx, imagesFromIndex(idx)[0])
87
+	assert.NilError(t, err)
88
+
89
+	t.Run("export without specific platform", func(t *testing.T) {
90
+		t.Skip("TODO(vvoland): https://github.com/docker/cli/issues/5476")
91
+		err = imgSvc.ExportImage(ctx, []string{img.Name}, nil, io.Discard)
92
+		assert.NilError(t, err)
93
+	})
94
+	t.Run("export native", func(t *testing.T) {
95
+		err = imgSvc.ExportImage(ctx, []string{img.Name}, &native, io.Discard)
96
+		assert.Check(t, is.ErrorType(err, errdefs.IsNotFound))
97
+	})
98
+	t.Run("export arm64", func(t *testing.T) {
99
+		err = imgSvc.ExportImage(ctx, []string{img.Name}, &arm64, io.Discard)
100
+		assert.NilError(t, err)
101
+	})
102
+}
0 103
new file mode 100644
... ...
@@ -0,0 +1,56 @@
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.Format(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
+		platform := platform
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
+}