Skip to content

Commit

Permalink
integration/save: Add tests checking OCI archive output
Browse files Browse the repository at this point in the history
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit 2ef0b53)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
  • Loading branch information
vvoland committed Feb 5, 2024
1 parent 225e043 commit 147b538
Show file tree
Hide file tree
Showing 4 changed files with 338 additions and 26 deletions.
131 changes: 107 additions & 24 deletions integration/image/save_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/integration/internal/build"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/testutils"
"github.com/docker/docker/internal/testutils/specialimage"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/testutil/fakecontext"
"github.com/opencontainers/go-digest"
Expand Down Expand Up @@ -88,45 +90,126 @@ func TestSaveCheckTimes(t *testing.T) {
}

// Regression test for https://github.com/moby/moby/issues/47065
func TestSaveCheckManifestLayers(t *testing.T) {
func TestSaveOCI(t *testing.T) {
skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.44"), "OCI layout support was introduced in v25")

ctx := setupTest(t)
client := testEnv.APIClient()

t.Parallel()

const repoName = "busybox:latest"
img, _, err := client.ImageInspectWithRaw(ctx, repoName)
const busybox = "busybox:latest"
inspectBusybox, _, err := client.ImageInspectWithRaw(ctx, busybox)
assert.NilError(t, err)

rdr, err := client.ImageSave(ctx, []string{repoName})
assert.NilError(t, err)
type testCase struct {
image string
expectedOCIRef string
expectedContainerdRef string
}

tarfs := tarIndexFS(t, rdr)
testCases := []testCase{
// Busybox by tagged name
testCase{image: busybox, expectedContainerdRef: "docker.io/library/busybox:latest", expectedOCIRef: "latest"},

indexData, err := fs.ReadFile(tarfs, "index.json")
assert.NilError(t, err)
// Busybox by ID
testCase{image: inspectBusybox.ID},
}

var index ocispec.Index
assert.NilError(t, json.Unmarshal(indexData, &index))
if testEnv.DaemonInfo.OSType != "windows" {
multiLayerImage := specialimage.Load(ctx, t, client, specialimage.MultiLayer)
// Multi-layer image
testCases = append(testCases, testCase{image: multiLayerImage, expectedContainerdRef: "docker.io/library/multilayer:latest", expectedOCIRef: "latest"})

assert.Assert(t, is.Len(index.Manifests, 1))
}

manifestData, err := fs.ReadFile(tarfs, "blobs/sha256/"+index.Manifests[0].Digest.Encoded())
assert.NilError(t, err)
// Busybox frozen image will have empty RepoDigests when loaded into the
// graphdriver image store so we can't use it.
// This will work with the containerd image store though.
if len(inspectBusybox.RepoDigests) > 0 {
// Digested reference
testCases = append(testCases, testCase{
image: inspectBusybox.RepoDigests[0],
})
}

var manifest ocispec.Manifest
assert.NilError(t, json.Unmarshal(manifestData, &manifest))
for _, tc := range testCases {
tc := tc
t.Run(tc.image, func(t *testing.T) {
// Get information about the original image.
inspect, _, err := client.ImageInspectWithRaw(ctx, tc.image)
assert.NilError(t, err)

assert.Check(t, is.Len(manifest.Layers, len(img.RootFS.Layers)))
for _, l := range manifest.Layers {
stat, err := fs.Stat(tarfs, "blobs/sha256/"+l.Digest.Encoded())
if !assert.Check(t, err) {
continue
}
rdr, err := client.ImageSave(ctx, []string{tc.image})
assert.NilError(t, err)
defer rdr.Close()

tarfs := tarIndexFS(t, rdr)

indexData, err := fs.ReadFile(tarfs, "index.json")
assert.NilError(t, err, "failed to read index.json")

var index ocispec.Index
assert.NilError(t, json.Unmarshal(indexData, &index), "failed to unmarshal index.json")

assert.Check(t, is.Equal(l.Size, stat.Size()))
// All test images are single-platform, so they should have only one manifest.
assert.Assert(t, is.Len(index.Manifests, 1))

manifestData, err := fs.ReadFile(tarfs, "blobs/sha256/"+index.Manifests[0].Digest.Encoded())
assert.NilError(t, err)

var manifest ocispec.Manifest
assert.NilError(t, json.Unmarshal(manifestData, &manifest))

t.Run("Manifest", func(t *testing.T) {
assert.Check(t, is.Len(manifest.Layers, len(inspect.RootFS.Layers)))

var digests []string
// Check if layers referenced by the manifest exist in the archive
// and match the layers from the original image.
for _, l := range manifest.Layers {
layerPath := "blobs/sha256/" + l.Digest.Encoded()
stat, err := fs.Stat(tarfs, layerPath)
assert.NilError(t, err)

assert.Check(t, is.Equal(l.Size, stat.Size()))

f, err := tarfs.Open(layerPath)
assert.NilError(t, err)

layerDigest, err := testutils.UncompressedTarDigest(f)
f.Close()

assert.NilError(t, err)

digests = append(digests, layerDigest.String())
}

assert.Check(t, is.DeepEqual(digests, inspect.RootFS.Layers))
})

t.Run("Config", func(t *testing.T) {
configData, err := fs.ReadFile(tarfs, "blobs/sha256/"+manifest.Config.Digest.Encoded())
assert.NilError(t, err)

var config ocispec.Image
assert.NilError(t, json.Unmarshal(configData, &config))

var diffIDs []string
for _, l := range config.RootFS.DiffIDs {
diffIDs = append(diffIDs, l.String())
}

assert.Check(t, is.DeepEqual(diffIDs, inspect.RootFS.Layers))
})

t.Run("Containerd image name", func(t *testing.T) {
assert.Check(t, is.Equal(index.Manifests[0].Annotations["io.containerd.image.name"], tc.expectedContainerdRef))
})

t.Run("OCI reference tag", func(t *testing.T) {
assert.Check(t, is.Equal(index.Manifests[0].Annotations["org.opencontainers.image.ref.name"], tc.expectedOCIRef))
})

})
}
}

Expand Down
24 changes: 24 additions & 0 deletions internal/testutils/archive.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package testutils

import (
"io"

"github.com/docker/docker/pkg/archive"
"github.com/opencontainers/go-digest"
)

// UncompressedTarDigest returns the canonical digest of the uncompressed tar stream.
func UncompressedTarDigest(compressedTar io.Reader) (digest.Digest, error) {
rd, err := archive.DecompressStream(compressedTar)
if err != nil {
return "", err
}

defer rd.Close()

digester := digest.Canonical.Digester()
if _, err := io.Copy(digester.Hash(), rd); err != nil {
return "", err
}
return digester.Digest(), nil
}
8 changes: 6 additions & 2 deletions internal/testutils/specialimage/load.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package specialimage

import (
"bytes"
"context"
"encoding/json"
"errors"
Expand Down Expand Up @@ -41,7 +42,10 @@ func Load(ctx context.Context, t *testing.T, apiClient client.APIClient, imageFu
t.Fatalf("Failed load: %s", string(respBody))
}

decoder := json.NewDecoder(resp.Body)
all, err := io.ReadAll(resp.Body)
assert.NilError(t, err)

decoder := json.NewDecoder(bytes.NewReader(all))
for {
var msg jsonmessage.JSONMessage
err := decoder.Decode(&msg)
Expand All @@ -61,6 +65,6 @@ func Load(ctx context.Context, t *testing.T, apiClient client.APIClient, imageFu
}
}

t.Fatal("failed to read image ID")
t.Fatalf("failed to read image ID\n%s", string(all))
return ""
}

0 comments on commit 147b538

Please sign in to comment.