moby/integration/image/save_test.go
Brian Goff e8dc902781 Wire up tests to support otel tracing
Integration tests will now configure clients to propagate traces as well
as create spans for all tests.

Some extra changes were needed (or desired for trace propagation) in the
test helpers to pass through tracing spans via context.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
2023-09-07 18:38:22 +00:00

238 lines
6 KiB
Go

package image
import (
"archive/tar"
"context"
"encoding/json"
"io"
"io/fs"
"os"
"path/filepath"
"reflect"
"sort"
"strings"
"testing"
"time"
"github.com/cpuguy83/tar2go"
"github.com/docker/docker/api/types"
containerapi "github.com/docker/docker/api/types/container"
"github.com/docker/docker/integration/internal/build"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/testutil/fakecontext"
"github.com/opencontainers/go-digest"
"gotest.tools/v3/assert"
"gotest.tools/v3/assert/cmp"
"gotest.tools/v3/skip"
)
type imageSaveManifestEntry struct {
Config string
RepoTags []string
Layers []string
}
func tarIndexFS(t *testing.T, rdr io.Reader) fs.FS {
t.Helper()
dir := t.TempDir()
f, err := os.Create(filepath.Join(dir, "image.tar"))
assert.NilError(t, err)
// Do not close at the end of this function otherwise the indexer won't work
t.Cleanup(func() { f.Close() })
_, err = io.Copy(f, rdr)
assert.NilError(t, err)
return tar2go.NewIndex(f).FS()
}
func TestSaveCheckTimes(t *testing.T) {
t.Parallel()
ctx := setupTest(t)
client := testEnv.APIClient()
const repoName = "busybox:latest"
img, _, err := client.ImageInspectWithRaw(ctx, repoName)
assert.NilError(t, err)
rdr, err := client.ImageSave(ctx, []string{repoName})
assert.NilError(t, err)
tarfs := tarIndexFS(t, rdr)
dt, err := fs.ReadFile(tarfs, "manifest.json")
assert.NilError(t, err)
var ls []imageSaveManifestEntry
assert.NilError(t, json.Unmarshal(dt, &ls))
assert.Assert(t, cmp.Len(ls, 1))
info, err := fs.Stat(tarfs, ls[0].Config)
assert.NilError(t, err)
created, err := time.Parse(time.RFC3339, img.Created)
assert.NilError(t, err)
assert.Equal(t, created.Format(time.RFC3339), info.ModTime().Format(time.RFC3339), "expected: %s, actual: %s", created, info.ModTime())
}
func TestSaveRepoWithMultipleImages(t *testing.T) {
ctx := setupTest(t)
client := testEnv.APIClient()
makeImage := func(from string, tag string) string {
id := container.Run(ctx, t, client, func(cfg *container.TestContainerConfig) {
cfg.Config.Image = from
cfg.Config.Cmd = []string{"true"}
})
chW, chErr := client.ContainerWait(ctx, id, containerapi.WaitConditionNotRunning)
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
select {
case <-chW:
case err := <-chErr:
assert.NilError(t, err)
case <-ctx.Done():
t.Fatal("timeout waiting for container to exit")
}
res, err := client.ContainerCommit(ctx, id, types.ContainerCommitOptions{Reference: tag})
assert.NilError(t, err)
err = client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{Force: true})
assert.NilError(t, err)
return res.ID
}
repoName := "foobar-save-multi-images-test"
tagFoo := repoName + ":foo"
tagBar := repoName + ":bar"
idFoo := makeImage("busybox:latest", tagFoo)
idBar := makeImage("busybox:latest", tagBar)
client.ImageRemove(ctx, repoName, types.ImageRemoveOptions{Force: true})
rdr, err := client.ImageSave(ctx, []string{repoName, "busybox:latest"})
assert.NilError(t, err)
defer rdr.Close()
tarfs := tarIndexFS(t, rdr)
dt, err := fs.ReadFile(tarfs, "manifest.json")
assert.NilError(t, err)
var mfstLs []imageSaveManifestEntry
assert.NilError(t, json.Unmarshal(dt, &mfstLs))
actual := make([]string, 0, len(mfstLs))
for _, m := range mfstLs {
actual = append(actual, strings.TrimPrefix(m.Config, "blobs/sha256/"))
// make sure the blob actually exists
_, err := fs.Stat(tarfs, m.Config)
assert.Check(t, cmp.Nil(err))
}
// make the list of expected layers
img, _, err := client.ImageInspectWithRaw(ctx, "busybox:latest")
assert.NilError(t, err)
expected := []string{img.ID, idFoo, idBar}
// prefixes are not in tar
for i := range expected {
expected[i] = digest.Digest(expected[i]).Encoded()
}
sort.Strings(actual)
sort.Strings(expected)
assert.Assert(t, cmp.DeepEqual(actual, expected), "archive does not contains the right layers: got %v, expected %v", actual, expected)
}
func TestSaveDirectoryPermissions(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "Test is looking at linux specific details")
ctx := setupTest(t)
client := testEnv.APIClient()
layerEntries := []string{"opt/", "opt/a/", "opt/a/b/", "opt/a/b/c"}
layerEntriesAUFS := []string{"./", ".wh..wh.aufs", ".wh..wh.orph/", ".wh..wh.plnk/", "opt/", "opt/a/", "opt/a/b/", "opt/a/b/c"}
dockerfile := `FROM busybox
RUN adduser -D user && mkdir -p /opt/a/b && chown -R user:user /opt/a
RUN touch /opt/a/b/c && chown user:user /opt/a/b/c`
imgID := build.Do(ctx, t, client, fakecontext.New(t, t.TempDir(), fakecontext.WithDockerfile(dockerfile)))
rdr, err := client.ImageSave(ctx, []string{imgID})
assert.NilError(t, err)
defer rdr.Close()
tarfs := tarIndexFS(t, rdr)
dt, err := fs.ReadFile(tarfs, "manifest.json")
assert.NilError(t, err)
var mfstLs []imageSaveManifestEntry
assert.NilError(t, json.Unmarshal(dt, &mfstLs))
var found bool
for _, p := range mfstLs[0].Layers {
var entriesSansDev []string
f, err := tarfs.Open(p)
assert.NilError(t, err)
entries, err := listTar(f)
f.Close()
assert.NilError(t, err)
for _, e := range entries {
if !strings.Contains(e, "dev/") {
entriesSansDev = append(entriesSansDev, e)
}
}
assert.NilError(t, err, "encountered error while listing tar entries: %s", err)
if reflect.DeepEqual(entriesSansDev, layerEntries) || reflect.DeepEqual(entriesSansDev, layerEntriesAUFS) {
found = true
break
}
}
assert.Assert(t, found, "failed to find the layer with the right content listing")
}
func listTar(f io.Reader) ([]string, error) {
// If using the containerd snapshotter, the tar file may be compressed
dec, err := archive.DecompressStream(f)
if err != nil {
return nil, err
}
defer dec.Close()
tr := tar.NewReader(dec)
var entries []string
for {
th, err := tr.Next()
if err == io.EOF {
// end of tar archive
return entries, nil
}
if err != nil {
return entries, err
}
entries = append(entries, th.Name)
}
}