github.com/containerd/Containerd@v1.4.13/import_test.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package containerd
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"encoding/json"
    23  	"io"
    24  
    25  	"io/ioutil"
    26  	"math/rand"
    27  	"reflect"
    28  	"runtime"
    29  	"testing"
    30  
    31  	"github.com/containerd/containerd/archive/compression"
    32  	"github.com/containerd/containerd/archive/tartest"
    33  	"github.com/containerd/containerd/images"
    34  	"github.com/containerd/containerd/images/archive"
    35  	digest "github.com/opencontainers/go-digest"
    36  	specs "github.com/opencontainers/image-spec/specs-go"
    37  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    38  )
    39  
    40  // TestExportAndImport exports testImage as a tar stream,
    41  // and import the tar stream as a new image.
    42  func TestExportAndImport(t *testing.T) {
    43  	// TODO: support windows
    44  	if testing.Short() || runtime.GOOS == "windows" {
    45  		t.Skip()
    46  	}
    47  	ctx, cancel := testContext(t)
    48  	defer cancel()
    49  
    50  	client, err := New(address)
    51  	if err != nil {
    52  		t.Fatal(err)
    53  	}
    54  	defer client.Close()
    55  
    56  	_, err = client.Fetch(ctx, testImage)
    57  	if err != nil {
    58  		t.Fatal(err)
    59  	}
    60  
    61  	wb := bytes.NewBuffer(nil)
    62  	err = client.Export(ctx, wb, archive.WithAllPlatforms(), archive.WithImage(client.ImageService(), testImage))
    63  	if err != nil {
    64  		t.Fatal(err)
    65  	}
    66  
    67  	opts := []ImportOpt{
    68  		WithImageRefTranslator(archive.AddRefPrefix("foo/bar")),
    69  	}
    70  	imgrecs, err := client.Import(ctx, bytes.NewReader(wb.Bytes()), opts...)
    71  	if err != nil {
    72  		t.Fatalf("Import failed: %+v", err)
    73  	}
    74  
    75  	for _, imgrec := range imgrecs {
    76  		if imgrec.Name == testImage {
    77  			continue
    78  		}
    79  		err = client.ImageService().Delete(ctx, imgrec.Name)
    80  		if err != nil {
    81  			t.Fatal(err)
    82  		}
    83  	}
    84  }
    85  
    86  func TestImport(t *testing.T) {
    87  	ctx, cancel := testContext(t)
    88  	defer cancel()
    89  
    90  	client, err := New(address)
    91  	if err != nil {
    92  		t.Fatal(err)
    93  	}
    94  	defer client.Close()
    95  
    96  	tc := tartest.TarContext{}
    97  
    98  	b1, d1 := createContent(256, 1)
    99  	empty := []byte("{}")
   100  	version := []byte("1.0")
   101  
   102  	c1, d2 := createConfig()
   103  
   104  	m1, d3, expManifest := createManifest(c1, [][]byte{b1})
   105  
   106  	provider := client.ContentStore()
   107  
   108  	checkManifest := func(ctx context.Context, t *testing.T, d ocispec.Descriptor, expManifest *ocispec.Manifest) {
   109  		m, err := images.Manifest(ctx, provider, d, nil)
   110  		if err != nil {
   111  			t.Fatalf("unable to read target blob: %+v", err)
   112  		}
   113  
   114  		if m.Config.Digest != d2 {
   115  			t.Fatalf("unexpected digest hash %s, expected %s", m.Config.Digest, d2)
   116  		}
   117  
   118  		if len(m.Layers) != 1 {
   119  			t.Fatalf("expected 1 layer, has %d", len(m.Layers))
   120  		}
   121  
   122  		if m.Layers[0].Digest != d1 {
   123  			t.Fatalf("unexpected layer hash %s, expected %s", m.Layers[0].Digest, d1)
   124  		}
   125  
   126  		if expManifest != nil {
   127  			if !reflect.DeepEqual(m.Layers, expManifest.Layers) {
   128  				t.Fatalf("DeepEqual on Layers failed: %v vs. %v", m.Layers, expManifest.Layers)
   129  			}
   130  			if !reflect.DeepEqual(m.Config, expManifest.Config) {
   131  				t.Fatalf("DeepEqual on Config failed: %v vs. %v", m.Config, expManifest.Config)
   132  			}
   133  		}
   134  	}
   135  
   136  	for _, tc := range []struct {
   137  		Name   string
   138  		Writer tartest.WriterToTar
   139  		Check  func(*testing.T, []images.Image)
   140  		Opts   []ImportOpt
   141  	}{
   142  		{
   143  			Name: "DockerV2.0",
   144  			Writer: tartest.TarAll(
   145  				tc.Dir("bd765cd43e95212f7aa2cab51d0a", 0755),
   146  				tc.File("bd765cd43e95212f7aa2cab51d0a/json", empty, 0644),
   147  				tc.File("bd765cd43e95212f7aa2cab51d0a/layer.tar", b1, 0644),
   148  				tc.File("bd765cd43e95212f7aa2cab51d0a/VERSION", version, 0644),
   149  				tc.File("repositories", []byte(`{"any":{"1":"bd765cd43e95212f7aa2cab51d0a"}}`), 0644),
   150  			),
   151  		},
   152  		{
   153  			Name: "DockerV2.1",
   154  			Writer: tartest.TarAll(
   155  				tc.Dir("bd765cd43e95212f7aa2cab51d0a", 0755),
   156  				tc.File("bd765cd43e95212f7aa2cab51d0a/json", empty, 0644),
   157  				tc.File("bd765cd43e95212f7aa2cab51d0a/layer.tar", b1, 0644),
   158  				tc.File("bd765cd43e95212f7aa2cab51d0a/VERSION", version, 0644),
   159  				tc.File("e95212f7aa2cab51d0abd765cd43.json", c1, 0644),
   160  				tc.File("manifest.json", []byte(`[{"Config":"e95212f7aa2cab51d0abd765cd43.json","RepoTags":["test-import:notlatest", "another/repo:tag"],"Layers":["bd765cd43e95212f7aa2cab51d0a/layer.tar"]}]`), 0644),
   161  			),
   162  			Check: func(t *testing.T, imgs []images.Image) {
   163  				if len(imgs) == 0 {
   164  					t.Fatalf("no images")
   165  				}
   166  
   167  				names := []string{
   168  					"docker.io/library/test-import:notlatest",
   169  					"docker.io/another/repo:tag",
   170  				}
   171  
   172  				checkImages(t, imgs[0].Target.Digest, imgs, names...)
   173  				checkManifest(ctx, t, imgs[0].Target, nil)
   174  			},
   175  		},
   176  		{
   177  			Name: "OCI-BadFormat",
   178  			Writer: tartest.TarAll(
   179  				tc.File("oci-layout", []byte(`{"imageLayoutVersion":"2.0.0"}`), 0644),
   180  			),
   181  		},
   182  		{
   183  			Name: "OCI",
   184  			Writer: tartest.TarAll(
   185  				tc.Dir("blobs", 0755),
   186  				tc.Dir("blobs/sha256", 0755),
   187  				tc.File("blobs/sha256/"+d1.Encoded(), b1, 0644),
   188  				tc.File("blobs/sha256/"+d2.Encoded(), c1, 0644),
   189  				tc.File("blobs/sha256/"+d3.Encoded(), m1, 0644),
   190  				tc.File("index.json", createIndex(m1, "latest", "docker.io/lib/img:ok"), 0644),
   191  				tc.File("oci-layout", []byte(`{"imageLayoutVersion":"1.0.0"}`), 0644),
   192  			),
   193  			Check: func(t *testing.T, imgs []images.Image) {
   194  				names := []string{
   195  					"latest",
   196  					"docker.io/lib/img:ok",
   197  				}
   198  
   199  				checkImages(t, d3, imgs, names...)
   200  				checkManifest(ctx, t, imgs[0].Target, expManifest)
   201  			},
   202  		},
   203  		{
   204  			Name: "OCIPrefixName",
   205  			Writer: tartest.TarAll(
   206  				tc.Dir("blobs", 0755),
   207  				tc.Dir("blobs/sha256", 0755),
   208  				tc.File("blobs/sha256/"+d1.Encoded(), b1, 0644),
   209  				tc.File("blobs/sha256/"+d2.Encoded(), c1, 0644),
   210  				tc.File("blobs/sha256/"+d3.Encoded(), m1, 0644),
   211  				tc.File("index.json", createIndex(m1, "latest", "docker.io/lib/img:ok"), 0644),
   212  				tc.File("oci-layout", []byte(`{"imageLayoutVersion":"1.0.0"}`), 0644),
   213  			),
   214  			Check: func(t *testing.T, imgs []images.Image) {
   215  				names := []string{
   216  					"localhost:5000/myimage:latest",
   217  					"docker.io/lib/img:ok",
   218  				}
   219  
   220  				checkImages(t, d3, imgs, names...)
   221  				checkManifest(ctx, t, imgs[0].Target, expManifest)
   222  			},
   223  			Opts: []ImportOpt{
   224  				WithImageRefTranslator(archive.AddRefPrefix("localhost:5000/myimage")),
   225  			},
   226  		},
   227  		{
   228  			Name: "OCIPrefixName2",
   229  			Writer: tartest.TarAll(
   230  				tc.Dir("blobs", 0755),
   231  				tc.Dir("blobs/sha256", 0755),
   232  				tc.File("blobs/sha256/"+d1.Encoded(), b1, 0644),
   233  				tc.File("blobs/sha256/"+d2.Encoded(), c1, 0644),
   234  				tc.File("blobs/sha256/"+d3.Encoded(), m1, 0644),
   235  				tc.File("index.json", createIndex(m1, "latest", "localhost:5000/myimage:old", "docker.io/lib/img:ok"), 0644),
   236  				tc.File("oci-layout", []byte(`{"imageLayoutVersion":"1.0.0"}`), 0644),
   237  			),
   238  			Check: func(t *testing.T, imgs []images.Image) {
   239  				names := []string{
   240  					"localhost:5000/myimage:latest",
   241  					"localhost:5000/myimage:old",
   242  				}
   243  
   244  				checkImages(t, d3, imgs, names...)
   245  				checkManifest(ctx, t, imgs[0].Target, expManifest)
   246  			},
   247  			Opts: []ImportOpt{
   248  				WithImageRefTranslator(archive.FilterRefPrefix("localhost:5000/myimage")),
   249  			},
   250  		},
   251  	} {
   252  		t.Run(tc.Name, func(t *testing.T) {
   253  			images, err := client.Import(ctx, tartest.TarFromWriterTo(tc.Writer), tc.Opts...)
   254  			if err != nil {
   255  				if tc.Check != nil {
   256  					t.Errorf("unexpected import error: %+v", err)
   257  				}
   258  				return
   259  			} else if tc.Check == nil {
   260  				t.Fatalf("expected error on import")
   261  			}
   262  
   263  			tc.Check(t, images)
   264  		})
   265  	}
   266  }
   267  
   268  func checkImages(t *testing.T, target digest.Digest, actual []images.Image, names ...string) {
   269  	if len(names) != len(actual) {
   270  		t.Fatalf("expected %d images, got %d", len(names), len(actual))
   271  	}
   272  
   273  	for i, n := range names {
   274  		if actual[i].Target.Digest != target {
   275  			t.Fatalf("image(%d) unexpected target %s, expected %s", i, actual[i].Target.Digest, target)
   276  		}
   277  		if actual[i].Name != n {
   278  			t.Fatalf("image(%d) unexpected name %q, expected %q", i, actual[i].Name, n)
   279  		}
   280  
   281  		if actual[i].Target.MediaType != ocispec.MediaTypeImageManifest &&
   282  			actual[i].Target.MediaType != images.MediaTypeDockerSchema2Manifest {
   283  			t.Fatalf("image(%d) unexpected media type: %s", i, actual[i].Target.MediaType)
   284  		}
   285  	}
   286  }
   287  
   288  func createContent(size int64, seed int64) ([]byte, digest.Digest) {
   289  	b, err := ioutil.ReadAll(io.LimitReader(rand.New(rand.NewSource(seed)), size))
   290  	if err != nil {
   291  		panic(err)
   292  	}
   293  	wb := bytes.NewBuffer(nil)
   294  	cw, err := compression.CompressStream(wb, compression.Gzip)
   295  	if err != nil {
   296  		panic(err)
   297  	}
   298  
   299  	if _, err := cw.Write(b); err != nil {
   300  		panic(err)
   301  	}
   302  	b = wb.Bytes()
   303  	return b, digest.FromBytes(b)
   304  }
   305  
   306  func createConfig() ([]byte, digest.Digest) {
   307  	image := ocispec.Image{
   308  		OS:           "any",
   309  		Architecture: "any",
   310  		Author:       "test",
   311  	}
   312  	b, _ := json.Marshal(image)
   313  
   314  	return b, digest.FromBytes(b)
   315  }
   316  
   317  func createManifest(config []byte, layers [][]byte) ([]byte, digest.Digest, *ocispec.Manifest) {
   318  	manifest := ocispec.Manifest{
   319  		Versioned: specs.Versioned{
   320  			SchemaVersion: 2,
   321  		},
   322  		Config: ocispec.Descriptor{
   323  			MediaType: ocispec.MediaTypeImageConfig,
   324  			Digest:    digest.FromBytes(config),
   325  			Size:      int64(len(config)),
   326  			Annotations: map[string]string{
   327  				"ocispec": "manifest.config.descriptor",
   328  			},
   329  		},
   330  	}
   331  	for _, l := range layers {
   332  		manifest.Layers = append(manifest.Layers, ocispec.Descriptor{
   333  			MediaType: ocispec.MediaTypeImageLayer,
   334  			Digest:    digest.FromBytes(l),
   335  			Size:      int64(len(l)),
   336  			Annotations: map[string]string{
   337  				"ocispec": "manifest.layers.descriptor",
   338  			},
   339  		})
   340  	}
   341  
   342  	b, _ := json.Marshal(manifest)
   343  
   344  	return b, digest.FromBytes(b), &manifest
   345  }
   346  
   347  func createIndex(manifest []byte, tags ...string) []byte {
   348  	idx := ocispec.Index{
   349  		Versioned: specs.Versioned{
   350  			SchemaVersion: 2,
   351  		},
   352  	}
   353  	d := ocispec.Descriptor{
   354  		MediaType: ocispec.MediaTypeImageManifest,
   355  		Digest:    digest.FromBytes(manifest),
   356  		Size:      int64(len(manifest)),
   357  	}
   358  
   359  	if len(tags) == 0 {
   360  		idx.Manifests = append(idx.Manifests, d)
   361  	} else {
   362  		for _, t := range tags {
   363  			dt := d
   364  			dt.Annotations = map[string]string{
   365  				ocispec.AnnotationRefName: t,
   366  			}
   367  			idx.Manifests = append(idx.Manifests, dt)
   368  		}
   369  	}
   370  
   371  	b, _ := json.Marshal(idx)
   372  
   373  	return b
   374  }