github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/integration/image/save_test.go (about)

     1  package image
     2  
     3  import (
     4  	"archive/tar"
     5  	"context"
     6  	"encoding/json"
     7  	"io"
     8  	"io/fs"
     9  	"os"
    10  	"path/filepath"
    11  	"reflect"
    12  	"sort"
    13  	"strings"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/cpuguy83/tar2go"
    18  	"github.com/Prakhar-Agarwal-byte/moby/api/types"
    19  	containertypes "github.com/Prakhar-Agarwal-byte/moby/api/types/container"
    20  	"github.com/Prakhar-Agarwal-byte/moby/integration/internal/build"
    21  	"github.com/Prakhar-Agarwal-byte/moby/integration/internal/container"
    22  	"github.com/Prakhar-Agarwal-byte/moby/pkg/archive"
    23  	"github.com/Prakhar-Agarwal-byte/moby/testutil/fakecontext"
    24  	"github.com/opencontainers/go-digest"
    25  	"gotest.tools/v3/assert"
    26  	"gotest.tools/v3/assert/cmp"
    27  	"gotest.tools/v3/skip"
    28  )
    29  
    30  type imageSaveManifestEntry struct {
    31  	Config   string
    32  	RepoTags []string
    33  	Layers   []string
    34  }
    35  
    36  func tarIndexFS(t *testing.T, rdr io.Reader) fs.FS {
    37  	t.Helper()
    38  
    39  	dir := t.TempDir()
    40  
    41  	f, err := os.Create(filepath.Join(dir, "image.tar"))
    42  	assert.NilError(t, err)
    43  
    44  	// Do not close at the end of this function otherwise the indexer won't work
    45  	t.Cleanup(func() { f.Close() })
    46  
    47  	_, err = io.Copy(f, rdr)
    48  	assert.NilError(t, err)
    49  
    50  	return tar2go.NewIndex(f).FS()
    51  }
    52  
    53  func TestSaveCheckTimes(t *testing.T) {
    54  	t.Parallel()
    55  
    56  	ctx := setupTest(t)
    57  	client := testEnv.APIClient()
    58  
    59  	const repoName = "busybox:latest"
    60  	img, _, err := client.ImageInspectWithRaw(ctx, repoName)
    61  	assert.NilError(t, err)
    62  
    63  	rdr, err := client.ImageSave(ctx, []string{repoName})
    64  	assert.NilError(t, err)
    65  
    66  	tarfs := tarIndexFS(t, rdr)
    67  
    68  	dt, err := fs.ReadFile(tarfs, "manifest.json")
    69  	assert.NilError(t, err)
    70  
    71  	var ls []imageSaveManifestEntry
    72  	assert.NilError(t, json.Unmarshal(dt, &ls))
    73  	assert.Assert(t, cmp.Len(ls, 1))
    74  
    75  	info, err := fs.Stat(tarfs, ls[0].Config)
    76  	assert.NilError(t, err)
    77  
    78  	created, err := time.Parse(time.RFC3339, img.Created)
    79  	assert.NilError(t, err)
    80  
    81  	assert.Equal(t, created.Format(time.RFC3339), info.ModTime().Format(time.RFC3339), "expected: %s, actual: %s", created, info.ModTime())
    82  }
    83  
    84  func TestSaveRepoWithMultipleImages(t *testing.T) {
    85  	ctx := setupTest(t)
    86  	client := testEnv.APIClient()
    87  
    88  	makeImage := func(from string, tag string) string {
    89  		id := container.Run(ctx, t, client, func(cfg *container.TestContainerConfig) {
    90  			cfg.Config.Image = from
    91  			cfg.Config.Cmd = []string{"true"}
    92  		})
    93  
    94  		chW, chErr := client.ContainerWait(ctx, id, containertypes.WaitConditionNotRunning)
    95  
    96  		ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
    97  		defer cancel()
    98  
    99  		select {
   100  		case <-chW:
   101  		case err := <-chErr:
   102  			assert.NilError(t, err)
   103  		case <-ctx.Done():
   104  			t.Fatal("timeout waiting for container to exit")
   105  		}
   106  
   107  		res, err := client.ContainerCommit(ctx, id, containertypes.CommitOptions{Reference: tag})
   108  		assert.NilError(t, err)
   109  
   110  		err = client.ContainerRemove(ctx, id, containertypes.RemoveOptions{Force: true})
   111  		assert.NilError(t, err)
   112  
   113  		return res.ID
   114  	}
   115  
   116  	busyboxImg, _, err := client.ImageInspectWithRaw(ctx, "busybox:latest")
   117  	assert.NilError(t, err)
   118  
   119  	repoName := "foobar-save-multi-images-test"
   120  	tagFoo := repoName + ":foo"
   121  	tagBar := repoName + ":bar"
   122  
   123  	idFoo := makeImage("busybox:latest", tagFoo)
   124  	idBar := makeImage("busybox:latest", tagBar)
   125  	idBusybox := busyboxImg.ID
   126  
   127  	client.ImageRemove(ctx, repoName, types.ImageRemoveOptions{Force: true})
   128  
   129  	rdr, err := client.ImageSave(ctx, []string{repoName, "busybox:latest"})
   130  	assert.NilError(t, err)
   131  	defer rdr.Close()
   132  
   133  	tarfs := tarIndexFS(t, rdr)
   134  
   135  	dt, err := fs.ReadFile(tarfs, "manifest.json")
   136  	assert.NilError(t, err)
   137  
   138  	var mfstLs []imageSaveManifestEntry
   139  	assert.NilError(t, json.Unmarshal(dt, &mfstLs))
   140  
   141  	actual := make([]string, 0, len(mfstLs))
   142  	for _, m := range mfstLs {
   143  		actual = append(actual, strings.TrimPrefix(m.Config, "blobs/sha256/"))
   144  		// make sure the blob actually exists
   145  		_, err := fs.Stat(tarfs, m.Config)
   146  		assert.Check(t, cmp.Nil(err))
   147  	}
   148  
   149  	expected := []string{idBusybox, idFoo, idBar}
   150  	// prefixes are not in tar
   151  	for i := range expected {
   152  		expected[i] = digest.Digest(expected[i]).Encoded()
   153  	}
   154  
   155  	// With snapshotters, ID of the image is the ID of the manifest/index
   156  	// With graphdrivers, ID of the image is the ID of the image config
   157  	if testEnv.UsingSnapshotter() {
   158  		// ID of image won't match the Config ID from manifest.json
   159  		// Just check if manifests exist in blobs
   160  		for _, blob := range expected {
   161  			_, err := fs.Stat(tarfs, "blobs/sha256/"+blob)
   162  			assert.Check(t, cmp.Nil(err))
   163  		}
   164  	} else {
   165  		sort.Strings(actual)
   166  		sort.Strings(expected)
   167  		assert.Assert(t, cmp.DeepEqual(actual, expected), "archive does not contains the right layers: got %v, expected %v", actual, expected)
   168  	}
   169  }
   170  
   171  func TestSaveDirectoryPermissions(t *testing.T) {
   172  	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "Test is looking at linux specific details")
   173  
   174  	ctx := setupTest(t)
   175  	client := testEnv.APIClient()
   176  
   177  	layerEntries := []string{"opt/", "opt/a/", "opt/a/b/", "opt/a/b/c"}
   178  	layerEntriesAUFS := []string{"./", ".wh..wh.aufs", ".wh..wh.orph/", ".wh..wh.plnk/", "opt/", "opt/a/", "opt/a/b/", "opt/a/b/c"}
   179  
   180  	dockerfile := `FROM busybox
   181  RUN adduser -D user && mkdir -p /opt/a/b && chown -R user:user /opt/a
   182  RUN touch /opt/a/b/c && chown user:user /opt/a/b/c`
   183  
   184  	imgID := build.Do(ctx, t, client, fakecontext.New(t, t.TempDir(), fakecontext.WithDockerfile(dockerfile)))
   185  
   186  	rdr, err := client.ImageSave(ctx, []string{imgID})
   187  	assert.NilError(t, err)
   188  	defer rdr.Close()
   189  
   190  	tarfs := tarIndexFS(t, rdr)
   191  
   192  	dt, err := fs.ReadFile(tarfs, "manifest.json")
   193  	assert.NilError(t, err)
   194  
   195  	var mfstLs []imageSaveManifestEntry
   196  	assert.NilError(t, json.Unmarshal(dt, &mfstLs))
   197  
   198  	var found bool
   199  
   200  	for _, p := range mfstLs[0].Layers {
   201  		var entriesSansDev []string
   202  
   203  		f, err := tarfs.Open(p)
   204  		assert.NilError(t, err)
   205  
   206  		entries, err := listTar(f)
   207  		f.Close()
   208  		assert.NilError(t, err)
   209  
   210  		for _, e := range entries {
   211  			if !strings.Contains(e, "dev/") {
   212  				entriesSansDev = append(entriesSansDev, e)
   213  			}
   214  		}
   215  		assert.NilError(t, err, "encountered error while listing tar entries: %s", err)
   216  
   217  		if reflect.DeepEqual(entriesSansDev, layerEntries) || reflect.DeepEqual(entriesSansDev, layerEntriesAUFS) {
   218  			found = true
   219  			break
   220  		}
   221  	}
   222  
   223  	assert.Assert(t, found, "failed to find the layer with the right content listing")
   224  }
   225  
   226  func listTar(f io.Reader) ([]string, error) {
   227  	// If using the containerd snapshotter, the tar file may be compressed
   228  	dec, err := archive.DecompressStream(f)
   229  	if err != nil {
   230  		return nil, err
   231  	}
   232  	defer dec.Close()
   233  
   234  	tr := tar.NewReader(dec)
   235  	var entries []string
   236  
   237  	for {
   238  		th, err := tr.Next()
   239  		if err == io.EOF {
   240  			// end of tar archive
   241  			return entries, nil
   242  		}
   243  		if err != nil {
   244  			return entries, err
   245  		}
   246  		entries = append(entries, th.Name)
   247  	}
   248  }