github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/oci/casext/refname_dir_test.go (about)

     1  /*
     2   * umoci: Umoci Modifies Open Containers' Images
     3   * Copyright (C) 2016-2020 SUSE LLC
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *    http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  
    18  package casext
    19  
    20  import (
    21  	"archive/tar"
    22  	"bytes"
    23  	"context"
    24  	crand "crypto/rand"
    25  	"fmt"
    26  	"io"
    27  	"io/ioutil"
    28  	"math/rand"
    29  	"os"
    30  	"path/filepath"
    31  	"reflect"
    32  	"runtime"
    33  	"testing"
    34  	"time"
    35  
    36  	"github.com/opencontainers/go-digest"
    37  	ispecs "github.com/opencontainers/image-spec/specs-go"
    38  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    39  	"github.com/opencontainers/umoci/oci/cas/dir"
    40  	"github.com/opencontainers/umoci/oci/casext/mediatype"
    41  	"github.com/opencontainers/umoci/pkg/testutils"
    42  )
    43  
    44  const (
    45  	customMediaType       = "org.opensuse.our-new-type"
    46  	customTargetMediaType = "org.opensuse.our-new-TARGET-type"
    47  	unknownMediaType      = "org.opensuse.fake-manifest"
    48  )
    49  
    50  type fakeManifest struct {
    51  	Descriptor ispec.Descriptor `json:"descriptor"`
    52  	Data       []byte           `json:"data"`
    53  }
    54  
    55  func init() {
    56  	fakeManifestParser := mediatype.CustomJSONParser(fakeManifest{})
    57  
    58  	mediatype.RegisterParser(customMediaType, fakeManifestParser)
    59  	mediatype.RegisterTarget(customTargetMediaType)
    60  	mediatype.RegisterParser(customTargetMediaType, fakeManifestParser)
    61  }
    62  
    63  type descriptorMap struct {
    64  	index  ispec.Descriptor
    65  	result ispec.Descriptor
    66  }
    67  
    68  func randomTarData(t *testing.T, tw *tar.Writer) error {
    69  	// Add some files with random contents and random names.
    70  	for n := 0; n < 32; n++ {
    71  		size := rand.Intn(512 * 1024)
    72  
    73  		if err := tw.WriteHeader(&tar.Header{
    74  			Name:     testutils.RandomString(16),
    75  			Mode:     0755,
    76  			Uid:      rand.Intn(1337),
    77  			Gid:      rand.Intn(1337),
    78  			Size:     int64(size),
    79  			Typeflag: tar.TypeReg,
    80  		}); err != nil {
    81  			return fmt.Errorf("randomTarData WriteHeader %d", n)
    82  		}
    83  		if _, err := io.CopyN(tw, crand.Reader, int64(size)); err != nil {
    84  			return fmt.Errorf("randomTarData Write %d", n)
    85  		}
    86  	}
    87  	return nil
    88  }
    89  
    90  // fakeSetupEngine injects a variety of "fake" blobs which may not include a
    91  // full blob tree to test whether Walk and ResolveReference act sanely in the
    92  // face of unknown media types as well as arbitrary nesting of known media
    93  // types. The returned
    94  func fakeSetupEngine(t *testing.T, engineExt Engine) ([]descriptorMap, error) {
    95  	ctx := context.Background()
    96  	mapping := []descriptorMap{}
    97  
    98  	// Add some "normal" images that contain some layers and also have some
    99  	// index indirects. The multiple layers makes sure that we don't break the
   100  	// multi-level resolution.
   101  	// XXX: In future we'll have to make tests for platform matching.
   102  	for k := 0; k < 5; k++ {
   103  		n := 3
   104  		name := fmt.Sprintf("normal_img_%d", k)
   105  
   106  		layerData := make([]bytes.Buffer, n)
   107  
   108  		// Generate layer data.
   109  		for idx := range layerData {
   110  			tw := tar.NewWriter(&layerData[idx])
   111  			if err := randomTarData(t, tw); err != nil {
   112  				t.Fatalf("%s: error generating layer%d data: %+v", name, idx, err)
   113  			}
   114  			tw.Close()
   115  		}
   116  
   117  		// Insert all of the layers.
   118  		layerDescriptors := make([]ispec.Descriptor, n)
   119  		for idx, layer := range layerData {
   120  			digest, size, err := engineExt.PutBlob(ctx, &layer)
   121  			if err != nil {
   122  				t.Fatalf("%s: error putting layer%d blob: %+v", name, idx, err)
   123  			}
   124  			layerDescriptors[idx] = ispec.Descriptor{
   125  				MediaType: ispec.MediaTypeImageLayer,
   126  				Digest:    digest,
   127  				Size:      size,
   128  			}
   129  		}
   130  
   131  		// Create our config and insert it.
   132  		created := time.Now()
   133  		configDigest, configSize, err := engineExt.PutBlobJSON(ctx, ispec.Image{
   134  			Created:      &created,
   135  			Author:       "Jane Author <janesmith@example.com>",
   136  			Architecture: runtime.GOARCH,
   137  			OS:           runtime.GOOS,
   138  			RootFS: ispec.RootFS{
   139  				Type: "unknown",
   140  			},
   141  		})
   142  		if err != nil {
   143  			t.Fatalf("%s: error putting config blob: %+v", name, err)
   144  		}
   145  		configDescriptor := ispec.Descriptor{
   146  			MediaType: ispec.MediaTypeImageConfig,
   147  			Digest:    configDigest,
   148  			Size:      configSize,
   149  		}
   150  
   151  		// Create our manifest and insert it.
   152  		manifest := ispec.Manifest{
   153  			Versioned: ispecs.Versioned{
   154  				SchemaVersion: 2,
   155  			},
   156  			MediaType: ispec.MediaTypeImageManifest,
   157  			Config:    configDescriptor,
   158  		}
   159  		manifest.Layers = append(manifest.Layers, layerDescriptors...)
   160  
   161  		manifestDigest, manifestSize, err := engineExt.PutBlobJSON(ctx, manifest)
   162  		if err != nil {
   163  			t.Fatalf("%s: error putting manifest blob: %+v", name, err)
   164  		}
   165  		manifestDescriptor := ispec.Descriptor{
   166  			MediaType: ispec.MediaTypeImageManifest,
   167  			Digest:    manifestDigest,
   168  			Size:      manifestSize,
   169  			Annotations: map[string]string{
   170  				"name": name,
   171  			},
   172  		}
   173  
   174  		// Add extra index layers.
   175  		indexDescriptor := manifestDescriptor
   176  		for i := 0; i < k; i++ {
   177  			newIndex := ispec.Index{
   178  				Versioned: ispecs.Versioned{
   179  					SchemaVersion: 2,
   180  				},
   181  				MediaType: ispec.MediaTypeImageIndex,
   182  				Manifests: []ispec.Descriptor{indexDescriptor},
   183  			}
   184  			indexDigest, indexSize, err := engineExt.PutBlobJSON(ctx, newIndex)
   185  			if err != nil {
   186  				t.Fatalf("%s: error putting index-%d blob: %+v", name, i, err)
   187  			}
   188  			indexDescriptor = ispec.Descriptor{
   189  				MediaType: ispec.MediaTypeImageIndex,
   190  				Digest:    indexDigest,
   191  				Size:      indexSize,
   192  			}
   193  		}
   194  
   195  		mapping = append(mapping, descriptorMap{
   196  			index:  indexDescriptor,
   197  			result: manifestDescriptor,
   198  		})
   199  	}
   200  
   201  	// Add some blobs that have custom mediaTypes. This is loosely based on
   202  	// the previous section.
   203  	for k := 0; k < 5; k++ {
   204  		name := fmt.Sprintf("custom_img_%d", k)
   205  
   206  		// Create a fake customTargetMediaType (will be masked by a different
   207  		// target media-type above).
   208  		notTargetDigest, notTargetSize, err := engineExt.PutBlobJSON(ctx, fakeManifest{
   209  			Data: []byte("Hello, world!"),
   210  		})
   211  		if err != nil {
   212  			t.Fatalf("%s: error putting custom-manifest blob: %+v", name, err)
   213  		}
   214  		notTargetDescriptor := ispec.Descriptor{
   215  			MediaType: customTargetMediaType,
   216  			Digest:    notTargetDigest,
   217  			Size:      notTargetSize,
   218  			Annotations: map[string]string{
   219  				"name": name,
   220  			},
   221  		}
   222  
   223  		// Add extra custom non-target layers.
   224  		currentDescriptor := notTargetDescriptor
   225  		for i := 0; i < k; i++ {
   226  			newDigest, newSize, err := engineExt.PutBlobJSON(ctx, fakeManifest{
   227  				Descriptor: currentDescriptor,
   228  				Data:       []byte("intermediate non-target"),
   229  			})
   230  			if err != nil {
   231  				t.Fatalf("%s: error putting custom-(non)target-%d blob: %+v", name, i, err)
   232  			}
   233  			currentDescriptor = ispec.Descriptor{
   234  				MediaType: customMediaType,
   235  				Digest:    newDigest,
   236  				Size:      newSize,
   237  			}
   238  		}
   239  
   240  		// Add the *real* customTargetMediaType.
   241  		targetDigest, targetSize, err := engineExt.PutBlobJSON(ctx, fakeManifest{
   242  			Descriptor: currentDescriptor,
   243  			Data:       []byte("I am the real target!"),
   244  		})
   245  		if err != nil {
   246  			t.Fatalf("%s: error putting custom-manifest blob: %+v", name, err)
   247  		}
   248  		targetDescriptor := ispec.Descriptor{
   249  			MediaType: customTargetMediaType,
   250  			Digest:    targetDigest,
   251  			Size:      targetSize,
   252  			Annotations: map[string]string{
   253  				"name": name,
   254  			},
   255  		}
   256  
   257  		// Add extra custom non-target layers.
   258  		currentDescriptor = targetDescriptor
   259  		for i := 0; i < k; i++ {
   260  			newDigest, newSize, err := engineExt.PutBlobJSON(ctx, fakeManifest{
   261  				Descriptor: currentDescriptor,
   262  				Data:       []byte("intermediate non-target"),
   263  			})
   264  			if err != nil {
   265  				t.Fatalf("%s: error putting custom-(non)target-%d blob: %+v", name, i, err)
   266  			}
   267  			currentDescriptor = ispec.Descriptor{
   268  				MediaType: customMediaType,
   269  				Digest:    newDigest,
   270  				Size:      newSize,
   271  			}
   272  		}
   273  
   274  		// Add extra index layers.
   275  		indexDescriptor := currentDescriptor
   276  		for i := 0; i < k; i++ {
   277  			newIndex := ispec.Index{
   278  				Versioned: ispecs.Versioned{
   279  					SchemaVersion: 2,
   280  				},
   281  				MediaType: ispec.MediaTypeImageIndex,
   282  				Manifests: []ispec.Descriptor{indexDescriptor},
   283  			}
   284  			indexDigest, indexSize, err := engineExt.PutBlobJSON(ctx, newIndex)
   285  			if err != nil {
   286  				t.Fatalf("%s: error putting index-%d blob: %+v", name, i, err)
   287  			}
   288  			indexDescriptor = ispec.Descriptor{
   289  				MediaType: ispec.MediaTypeImageIndex,
   290  				Digest:    indexDigest,
   291  				Size:      indexSize,
   292  			}
   293  		}
   294  
   295  		mapping = append(mapping, descriptorMap{
   296  			index:  indexDescriptor,
   297  			result: targetDescriptor,
   298  		})
   299  	}
   300  
   301  	// Add some blobs that have unknown mediaTypes. This is loosely based on
   302  	// the previous section.
   303  	for k := 0; k < 5; k++ {
   304  		name := fmt.Sprintf("unknown_img_%d", k)
   305  
   306  		manifestDigest, manifestSize, err := engineExt.PutBlobJSON(ctx, fakeManifest{
   307  			Descriptor: ispec.Descriptor{
   308  				MediaType: "org.opensuse.fake-data",
   309  				Digest:    digest.SHA256.FromString("Hello, world!"),
   310  				Size:      0,
   311  			},
   312  			Data: []byte("Hello, world!"),
   313  		})
   314  		if err != nil {
   315  			t.Fatalf("%s: error putting manifest blob: %+v", name, err)
   316  		}
   317  		manifestDescriptor := ispec.Descriptor{
   318  			MediaType: unknownMediaType,
   319  			Digest:    manifestDigest,
   320  			Size:      manifestSize,
   321  			Annotations: map[string]string{
   322  				"name": name,
   323  			},
   324  		}
   325  
   326  		// Add extra index layers.
   327  		indexDescriptor := manifestDescriptor
   328  		for i := 0; i < k; i++ {
   329  			newIndex := ispec.Index{
   330  				Versioned: ispecs.Versioned{
   331  					SchemaVersion: 2,
   332  				},
   333  				MediaType: ispec.MediaTypeImageIndex,
   334  				Manifests: []ispec.Descriptor{indexDescriptor},
   335  			}
   336  			indexDigest, indexSize, err := engineExt.PutBlobJSON(ctx, newIndex)
   337  			if err != nil {
   338  				t.Fatalf("%s: error putting index-%d blob: %+v", name, i, err)
   339  			}
   340  			indexDescriptor = ispec.Descriptor{
   341  				MediaType: ispec.MediaTypeImageIndex,
   342  				Digest:    indexDigest,
   343  				Size:      indexSize,
   344  			}
   345  		}
   346  
   347  		mapping = append(mapping, descriptorMap{
   348  			index:  indexDescriptor,
   349  			result: manifestDescriptor,
   350  		})
   351  	}
   352  
   353  	return mapping, nil
   354  }
   355  
   356  func TestEngineReference(t *testing.T) {
   357  	ctx := context.Background()
   358  
   359  	root, err := ioutil.TempDir("", "umoci-TestEngineReference")
   360  	if err != nil {
   361  		t.Fatal(err)
   362  	}
   363  	defer os.RemoveAll(root)
   364  
   365  	image := filepath.Join(root, "image")
   366  	if err := dir.Create(image); err != nil {
   367  		t.Fatalf("unexpected error creating image: %+v", err)
   368  	}
   369  
   370  	engine, err := dir.Open(image)
   371  	if err != nil {
   372  		t.Fatalf("unexpected error opening image: %+v", err)
   373  	}
   374  	engineExt := NewEngine(engine)
   375  	defer engine.Close()
   376  
   377  	descMap, err := fakeSetupEngine(t, engineExt)
   378  	if err != nil {
   379  		t.Fatalf("unexpected error doing fakeSetupEngine: %+v", err)
   380  	}
   381  
   382  	for idx, test := range descMap {
   383  		name := fmt.Sprintf("new_tag_%d", idx)
   384  
   385  		if err := engineExt.UpdateReference(ctx, name, test.index); err != nil {
   386  			t.Errorf("UpdateReference: unexpected error: %+v", err)
   387  		}
   388  
   389  		gotDescriptorPaths, err := engineExt.ResolveReference(ctx, name)
   390  		if err != nil {
   391  			t.Errorf("ResolveReference: unexpected error: %+v", err)
   392  		}
   393  		if len(gotDescriptorPaths) != 1 {
   394  			t.Errorf("ResolveReference: expected %q to get %d descriptors, got %d: %+v", name, 1, len(gotDescriptorPaths), gotDescriptorPaths)
   395  			continue
   396  		}
   397  		gotDescriptor := gotDescriptorPaths[0].Descriptor()
   398  
   399  		if !reflect.DeepEqual(test.result, gotDescriptor) {
   400  			t.Errorf("ResolveReference: got different descriptor to original: expected=%v got=%v", test.result, gotDescriptor)
   401  		}
   402  
   403  		if err := engineExt.DeleteReference(ctx, name); err != nil {
   404  			t.Errorf("DeleteReference: unexpected error: %+v", err)
   405  		}
   406  
   407  		if gotDescriptorPaths, err := engineExt.ResolveReference(ctx, name); err != nil {
   408  			t.Errorf("ResolveReference: unexpected error: %+v", err)
   409  		} else if len(gotDescriptorPaths) > 0 {
   410  			t.Errorf("ResolveReference: still got reference descriptors after DeleteReference!")
   411  		}
   412  
   413  		// DeleteBlob is idempotent. It shouldn't cause an error.
   414  		if err := engineExt.DeleteReference(ctx, name); err != nil {
   415  			t.Errorf("DeleteReference: unexpected error on double-delete: %+v", err)
   416  		}
   417  	}
   418  }
   419  
   420  func TestEngineReferenceReadonly(t *testing.T) {
   421  	ctx := context.Background()
   422  
   423  	root, err := ioutil.TempDir("", "umoci-TestEngineReferenceReadonly")
   424  	if err != nil {
   425  		t.Fatal(err)
   426  	}
   427  	defer os.RemoveAll(root)
   428  
   429  	image := filepath.Join(root, "image")
   430  	if err := dir.Create(image); err != nil {
   431  		t.Fatalf("unexpected error creating image: %+v", err)
   432  	}
   433  
   434  	engine, err := dir.Open(image)
   435  	if err != nil {
   436  		t.Fatalf("unexpected error opening image: %+v", err)
   437  	}
   438  	engineExt := NewEngine(engine)
   439  
   440  	descMap, err := fakeSetupEngine(t, engineExt)
   441  	if err != nil {
   442  		t.Fatalf("unexpected error doing fakeSetupEngine: %+v", err)
   443  	}
   444  
   445  	if err := engine.Close(); err != nil {
   446  		t.Fatalf("unexpected error closing image: %+v", err)
   447  	}
   448  
   449  	for idx, test := range descMap {
   450  		name := fmt.Sprintf("new_tag_%d", idx)
   451  
   452  		engine, err := dir.Open(image)
   453  		if err != nil {
   454  			t.Fatalf("unexpected error opening image: %+v", err)
   455  		}
   456  		engineExt := NewEngine(engine)
   457  
   458  		if err := engineExt.UpdateReference(ctx, name, test.index); err != nil {
   459  			t.Errorf("UpdateReference: unexpected error: %+v", err)
   460  		}
   461  
   462  		if err := engine.Close(); err != nil {
   463  			t.Errorf("Close: unexpected error encountered: %+v", err)
   464  		}
   465  
   466  		// make it readonly
   467  		testutils.MakeReadOnly(t, image)
   468  
   469  		newEngine, err := dir.Open(image)
   470  		if err != nil {
   471  			t.Errorf("unexpected error opening ro image: %+v", err)
   472  		}
   473  		newEngineExt := NewEngine(newEngine)
   474  
   475  		gotDescriptorPaths, err := newEngineExt.ResolveReference(ctx, name)
   476  		if err != nil {
   477  			t.Errorf("ResolveReference: unexpected error: %+v", err)
   478  		}
   479  		if len(gotDescriptorPaths) != 1 {
   480  			t.Errorf("ResolveReference: expected to get %d descriptors, got %d: %+v", 1, len(gotDescriptorPaths), gotDescriptorPaths)
   481  		}
   482  		gotDescriptor := gotDescriptorPaths[0].Descriptor()
   483  
   484  		if !reflect.DeepEqual(test.result, gotDescriptor) {
   485  			t.Errorf("ResolveReference: got different descriptor to original: expected=%v got=%v", test.result, gotDescriptor)
   486  		}
   487  
   488  		// Make sure that writing will FAIL.
   489  		if err := newEngineExt.UpdateReference(ctx, name+"new", test.index); err == nil {
   490  			t.Errorf("UpdateReference: expected error on ro image!")
   491  		}
   492  
   493  		if err := newEngine.Close(); err != nil {
   494  			t.Errorf("Close: unexpected error encountered on ro: %+v", err)
   495  		}
   496  
   497  		// make it readwrite again.
   498  		testutils.MakeReadWrite(t, image)
   499  	}
   500  }