github.com/dctrud/umoci@v0.4.3-0.20191016193643-05a1d37de015/oci/casext/refname_dir_test.go (about)

     1  /*
     2   * umoci: Umoci Modifies Open Containers' Images
     3   * Copyright (C) 2016, 2017, 2018 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  	crand "crypto/rand"
    24  	"fmt"
    25  	"io"
    26  	"io/ioutil"
    27  	"math/rand"
    28  	"os"
    29  	"path/filepath"
    30  	"reflect"
    31  	"runtime"
    32  	"testing"
    33  	"time"
    34  
    35  	"github.com/openSUSE/umoci/oci/cas/dir"
    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  	"golang.org/x/net/context"
    40  )
    41  
    42  type descriptorMap struct {
    43  	index  ispec.Descriptor
    44  	result ispec.Descriptor
    45  }
    46  
    47  func randomTarData(t *testing.T, tw *tar.Writer) error {
    48  	// Add some files with random contents and random names.
    49  	for n := 0; n < 32; n++ {
    50  		size := rand.Intn(512 * 1024)
    51  
    52  		if err := tw.WriteHeader(&tar.Header{
    53  			Name:     randomString(16),
    54  			Mode:     0755,
    55  			Uid:      rand.Intn(1337),
    56  			Gid:      rand.Intn(1337),
    57  			Size:     int64(size),
    58  			Typeflag: tar.TypeReg,
    59  		}); err != nil {
    60  			return fmt.Errorf("randomTarData WriteHeader %d", n)
    61  		}
    62  		if _, err := io.CopyN(tw, crand.Reader, int64(size)); err != nil {
    63  			return fmt.Errorf("randomTarData Write %d", n)
    64  		}
    65  	}
    66  	return nil
    67  }
    68  
    69  // fakeSetupEngine injects a variety of "fake" blobs which may not include a
    70  // full blob tree to test whether Walk and ResolveReference act sanely in the
    71  // face of unknown media types as well as arbitrary nesting of known media
    72  // types. The returned
    73  func fakeSetupEngine(t *testing.T, engineExt Engine) ([]descriptorMap, error) {
    74  	ctx := context.Background()
    75  	mapping := []descriptorMap{}
    76  
    77  	// Add some "normal" images that contain some layers and also have some
    78  	// index indirects. The multiple layers makes sure that we don't break the
    79  	// multi-level resolution.
    80  	// XXX: In future we'll have to make tests for platform matching.
    81  	for k := 0; k < 5; k++ {
    82  		n := 3
    83  		name := fmt.Sprintf("img_%d", k)
    84  
    85  		layerData := make([]bytes.Buffer, n)
    86  
    87  		// Generate layer data.
    88  		for idx := range layerData {
    89  			tw := tar.NewWriter(&layerData[idx])
    90  			if err := randomTarData(t, tw); err != nil {
    91  				t.Fatalf("%s: error generating layer%d data: %+v", name, idx, err)
    92  			}
    93  			tw.Close()
    94  		}
    95  
    96  		// Insert all of the layers.
    97  		layerDescriptors := make([]ispec.Descriptor, n)
    98  		for idx, layer := range layerData {
    99  			digest, size, err := engineExt.PutBlob(ctx, &layer)
   100  			if err != nil {
   101  				t.Fatalf("%s: error putting layer%d blob: %+v", name, idx, err)
   102  			}
   103  			layerDescriptors[idx] = ispec.Descriptor{
   104  				MediaType: ispec.MediaTypeImageLayer,
   105  				Digest:    digest,
   106  				Size:      size,
   107  			}
   108  		}
   109  
   110  		// Create our config and insert it.
   111  		created := time.Now()
   112  		configDigest, configSize, err := engineExt.PutBlobJSON(ctx, ispec.Image{
   113  			Created:      &created,
   114  			Author:       "Jane Author <janesmith@example.com>",
   115  			Architecture: runtime.GOARCH,
   116  			OS:           runtime.GOOS,
   117  			RootFS: ispec.RootFS{
   118  				Type: "unknown",
   119  			},
   120  		})
   121  		if err != nil {
   122  			t.Fatalf("%s: error putting config blob: %+v", name, err)
   123  		}
   124  		configDescriptor := ispec.Descriptor{
   125  			MediaType: ispec.MediaTypeImageConfig,
   126  			Digest:    configDigest,
   127  			Size:      configSize,
   128  		}
   129  
   130  		// Create our manifest and insert it.
   131  		manifest := ispec.Manifest{
   132  			Versioned: ispecs.Versioned{
   133  				SchemaVersion: 2,
   134  			},
   135  			Config: configDescriptor,
   136  		}
   137  		for _, layer := range layerDescriptors {
   138  			manifest.Layers = append(manifest.Layers, layer)
   139  		}
   140  
   141  		manifestDigest, manifestSize, err := engineExt.PutBlobJSON(ctx, manifest)
   142  		if err != nil {
   143  			t.Fatalf("%s: error putting manifest blob: %+v", name, err)
   144  		}
   145  		manifestDescriptor := ispec.Descriptor{
   146  			MediaType: ispec.MediaTypeImageManifest,
   147  			Digest:    manifestDigest,
   148  			Size:      manifestSize,
   149  			Annotations: map[string]string{
   150  				"name": name,
   151  			},
   152  		}
   153  
   154  		// Add extra index layers.
   155  		indexDescriptor := manifestDescriptor
   156  		for i := 0; i < k; i++ {
   157  			newIndex := ispec.Index{
   158  				Versioned: ispecs.Versioned{
   159  					SchemaVersion: 2,
   160  				},
   161  				Manifests: []ispec.Descriptor{indexDescriptor},
   162  			}
   163  			indexDigest, indexSize, err := engineExt.PutBlobJSON(ctx, newIndex)
   164  			if err != nil {
   165  				t.Fatalf("%s: error putting index-%d blob: %+v", name, i, err)
   166  			}
   167  			indexDescriptor = ispec.Descriptor{
   168  				MediaType: ispec.MediaTypeImageIndex,
   169  				Digest:    indexDigest,
   170  				Size:      indexSize,
   171  			}
   172  		}
   173  
   174  		mapping = append(mapping, descriptorMap{
   175  			index:  indexDescriptor,
   176  			result: manifestDescriptor,
   177  		})
   178  	}
   179  
   180  	// Add some blobs that have unknown mediaTypes. This is loosely based on
   181  	// the previous section.
   182  	for k := 0; k < 5; k++ {
   183  		name := fmt.Sprintf("img_%d", k)
   184  
   185  		type fakeManifest struct {
   186  			Descriptor ispec.Descriptor `json:"descriptor"`
   187  			Data       []byte           `json:"data"`
   188  		}
   189  
   190  		manifestDigest, manifestSize, err := engineExt.PutBlobJSON(ctx, fakeManifest{
   191  			Descriptor: ispec.Descriptor{
   192  				MediaType: "org.opensuse.fake.data",
   193  				Digest:    digest.SHA256.FromString("Hello, world!"),
   194  				Size:      0,
   195  			},
   196  			Data: []byte("Hello, world!"),
   197  		})
   198  		if err != nil {
   199  			t.Fatalf("%s: error putting manifest blob: %+v", name, err)
   200  		}
   201  		manifestDescriptor := ispec.Descriptor{
   202  			MediaType: "org.opensuse.fake.manifest",
   203  			Digest:    manifestDigest,
   204  			Size:      manifestSize,
   205  			Annotations: map[string]string{
   206  				"name": name,
   207  			},
   208  		}
   209  
   210  		// Add extra index layers.
   211  		indexDescriptor := manifestDescriptor
   212  		for i := 0; i < k; i++ {
   213  			newIndex := ispec.Index{
   214  				Versioned: ispecs.Versioned{
   215  					SchemaVersion: 2,
   216  				},
   217  				Manifests: []ispec.Descriptor{indexDescriptor},
   218  			}
   219  			indexDigest, indexSize, err := engineExt.PutBlobJSON(ctx, newIndex)
   220  			if err != nil {
   221  				t.Fatalf("%s: error putting index-%d blob: %+v", name, i, err)
   222  			}
   223  			indexDescriptor = ispec.Descriptor{
   224  				MediaType: ispec.MediaTypeImageIndex,
   225  				Digest:    indexDigest,
   226  				Size:      indexSize,
   227  			}
   228  		}
   229  
   230  		mapping = append(mapping, descriptorMap{
   231  			index:  indexDescriptor,
   232  			result: manifestDescriptor,
   233  		})
   234  	}
   235  
   236  	return mapping, nil
   237  }
   238  
   239  func TestEngineReference(t *testing.T) {
   240  	ctx := context.Background()
   241  
   242  	root, err := ioutil.TempDir("", "umoci-TestEngineReference")
   243  	if err != nil {
   244  		t.Fatal(err)
   245  	}
   246  	defer os.RemoveAll(root)
   247  
   248  	image := filepath.Join(root, "image")
   249  	if err := dir.Create(image); err != nil {
   250  		t.Fatalf("unexpected error creating image: %+v", err)
   251  	}
   252  
   253  	engine, err := dir.Open(image)
   254  	if err != nil {
   255  		t.Fatalf("unexpected error opening image: %+v", err)
   256  	}
   257  	engineExt := NewEngine(engine)
   258  	defer engine.Close()
   259  
   260  	descMap, err := fakeSetupEngine(t, engineExt)
   261  	if err != nil {
   262  		t.Fatalf("unexpected error doing fakeSetupEngine: %+v", err)
   263  	}
   264  
   265  	for idx, test := range descMap {
   266  		name := fmt.Sprintf("new_tag_%d", idx)
   267  
   268  		if err := engineExt.UpdateReference(ctx, name, test.index); err != nil {
   269  			t.Errorf("UpdateReference: unexpected error: %+v", err)
   270  		}
   271  
   272  		gotDescriptorPaths, err := engineExt.ResolveReference(ctx, name)
   273  		if err != nil {
   274  			t.Errorf("ResolveReference: unexpected error: %+v", err)
   275  		}
   276  		if len(gotDescriptorPaths) != 1 {
   277  			t.Errorf("ResolveReference: expected %q to get %d descriptors, got %d: %+v", name, 1, len(gotDescriptorPaths), gotDescriptorPaths)
   278  			continue
   279  		}
   280  		gotDescriptor := gotDescriptorPaths[0].Descriptor()
   281  
   282  		if !reflect.DeepEqual(test.result, gotDescriptor) {
   283  			t.Errorf("ResolveReference: got different descriptor to original: expected=%v got=%v", test.result, gotDescriptor)
   284  		}
   285  
   286  		if err := engineExt.DeleteReference(ctx, name); err != nil {
   287  			t.Errorf("DeleteReference: unexpected error: %+v", err)
   288  		}
   289  
   290  		if gotDescriptorPaths, err := engineExt.ResolveReference(ctx, name); err != nil {
   291  			t.Errorf("ResolveReference: unexpected error: %+v", err)
   292  		} else if len(gotDescriptorPaths) > 0 {
   293  			t.Errorf("ResolveReference: still got reference descriptors after DeleteReference!")
   294  		}
   295  
   296  		// DeleteBlob is idempotent. It shouldn't cause an error.
   297  		if err := engineExt.DeleteReference(ctx, name); err != nil {
   298  			t.Errorf("DeleteReference: unexpected error on double-delete: %+v", err)
   299  		}
   300  	}
   301  }
   302  
   303  func TestEngineReferenceReadonly(t *testing.T) {
   304  	ctx := context.Background()
   305  
   306  	root, err := ioutil.TempDir("", "umoci-TestEngineReferenceReadonly")
   307  	if err != nil {
   308  		t.Fatal(err)
   309  	}
   310  	defer os.RemoveAll(root)
   311  
   312  	image := filepath.Join(root, "image")
   313  	if err := dir.Create(image); err != nil {
   314  		t.Fatalf("unexpected error creating image: %+v", err)
   315  	}
   316  
   317  	engine, err := dir.Open(image)
   318  	if err != nil {
   319  		t.Fatalf("unexpected error opening image: %+v", err)
   320  	}
   321  	engineExt := NewEngine(engine)
   322  
   323  	descMap, err := fakeSetupEngine(t, engineExt)
   324  	if err != nil {
   325  		t.Fatalf("unexpected error doing fakeSetupEngine: %+v", err)
   326  	}
   327  
   328  	if err := engine.Close(); err != nil {
   329  		t.Fatalf("unexpected error closing image: %+v", err)
   330  	}
   331  
   332  	for idx, test := range descMap {
   333  		name := fmt.Sprintf("new_tag_%d", idx)
   334  
   335  		engine, err := dir.Open(image)
   336  		if err != nil {
   337  			t.Fatalf("unexpected error opening image: %+v", err)
   338  		}
   339  		engineExt := NewEngine(engine)
   340  
   341  		if err := engineExt.UpdateReference(ctx, name, test.index); err != nil {
   342  			t.Errorf("UpdateReference: unexpected error: %+v", err)
   343  		}
   344  
   345  		if err := engine.Close(); err != nil {
   346  			t.Errorf("Close: unexpected error encountered: %+v", err)
   347  		}
   348  
   349  		// make it readonly
   350  		readonly(t, image)
   351  
   352  		newEngine, err := dir.Open(image)
   353  		if err != nil {
   354  			t.Errorf("unexpected error opening ro image: %+v", err)
   355  		}
   356  		newEngineExt := NewEngine(newEngine)
   357  
   358  		gotDescriptorPaths, err := newEngineExt.ResolveReference(ctx, name)
   359  		if err != nil {
   360  			t.Errorf("ResolveReference: unexpected error: %+v", err)
   361  		}
   362  		if len(gotDescriptorPaths) != 1 {
   363  			t.Errorf("ResolveReference: expected to get %d descriptors, got %d: %+v", 1, len(gotDescriptorPaths), gotDescriptorPaths)
   364  		}
   365  		gotDescriptor := gotDescriptorPaths[0].Descriptor()
   366  
   367  		if !reflect.DeepEqual(test.result, gotDescriptor) {
   368  			t.Errorf("ResolveReference: got different descriptor to original: expected=%v got=%v", test.result, gotDescriptor)
   369  		}
   370  
   371  		// Make sure that writing will FAIL.
   372  		if err := newEngineExt.UpdateReference(ctx, name+"new", test.index); err == nil {
   373  			t.Errorf("UpdateReference: expected error on ro image!")
   374  		}
   375  
   376  		if err := newEngine.Close(); err != nil {
   377  			t.Errorf("Close: unexpected error encountered on ro: %+v", err)
   378  		}
   379  
   380  		// make it readwrite again.
   381  		readwrite(t, image)
   382  	}
   383  }