github.com/opencontainers/umoci@v0.4.8-0.20240508124516-656e4836fb0d/oci/layer/unpack_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 layer
    19  
    20  import (
    21  	"bytes"
    22  	"context"
    23  	"encoding/base64"
    24  	"io"
    25  	"io/ioutil"
    26  	"os"
    27  	"path/filepath"
    28  	"testing"
    29  
    30  	"github.com/opencontainers/go-digest"
    31  	"github.com/opencontainers/image-spec/specs-go"
    32  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    33  	rspec "github.com/opencontainers/runtime-spec/specs-go"
    34  	"github.com/opencontainers/umoci/oci/cas/dir"
    35  	"github.com/opencontainers/umoci/oci/casext"
    36  )
    37  
    38  func mustDecodeString(s string) []byte {
    39  	b, err := base64.StdEncoding.DecodeString(s)
    40  	if err != nil {
    41  		panic(err)
    42  	}
    43  	return b
    44  }
    45  
    46  func makeImage(t *testing.T) (string, ispec.Manifest, casext.Engine) {
    47  	ctx := context.Background()
    48  	// These layers were manually generated using GNU tar + GNU gzip.
    49  	// XXX: In future we should also add libarchive tar archives.
    50  	var layers = []struct {
    51  		base64 string
    52  		digest digest.Digest
    53  	}{
    54  		{
    55  			base64: `
    56  H4sIAAsoz1kAA+3XvW7CMBAH8Mx9Cj+Bcz7bZxjYO3brWEXBCCS+lBiJvn2dVColAUpUEop6v8UR
    57  jrGj6P7RyfQl2z/7bOqLUloNJpXJrUFExtRj1BxBaUyUVqQJtSWVgAJnVSL2Nz/JCbsyZEU8yhB7
    58  /UEaxCosVn6iLJAzI3RaIhEhGjJPcTZrzhLUs85Vs/n5tfd+MnYNmfa/R1XjztpqVM5+1r065EGd
    59  Bcf1rxxSImz/RzvUf/6+nceLC/fFhLzwP81wexCylf/Bl+Fttlj6m+3RKf+1ju8freP8H0Qr/62S
    60  qA2hInCt/NcAcgRYvyfbzv+jtfd+MnYN2UO9N32r/5P5r9A16j9exfwfpCb/ef6/zjci36xDsVmW
    61  Isy92GZlOP5ltgu7wkvRvrXwpV+H9nrJxf8oznz/p4sLpdBV9/4Pjebv/yC69X8/fP9HJMdYrR2P
    62  LUfAQ5Bf9d5fI1j3f+A69H/aGuD+jzHGGGOMMcYYY4wxxn7jA5XNY6oAKAAA`,
    63  			digest: digest.NewDigestFromHex(digest.SHA256.String(), "e489a16a8ca0d682394867ad8a8183f0a47cbad80b3134a83412a6796ad9242a"),
    64  		},
    65  		{
    66  			base64: `
    67  H4sIAJ4oz1kAA+3Wu27CMBQG4Mw8xSldK8d3p0OHbu3WN6hCYhELCMh2Bbx9HTogwkWtBLSo51uM
    68  dBLsSPl/heRv5erFlrX1gWimHnOSnRtNtJSbNemvlAmeMcG00FxyajLKqNE0g9XZT3LAR4ilT0e5
    69  xl5/kKAwi25mn5ii2shCKUaKgmshBOWDNC13p5xIvply0U2r4/f+9pOh7yD55ffoMm6U6lZm1Ffu
    70  2bYPNl2wm39muMxAXf5o2/xX60WTfpy4LjXkif/pl9uNIHv9H22I76HybhFJaM6xx8/7XyhjsP+v
    71  4WD/m+JY/2tBKOumRqj9/teUCCXTVAvs/9tALpD3vhRxear/mZC9/Kd3KH3/XSWT/7z/7+/ykWvz
    72  0AwGtmrmMHyNsCwDlDDybtxEqObTGupyDa6F54V30wco2xpiY6GazqtJgKX1FkL0buLacRo4H61t
    73  yRAbACGEEEIIIYQQQgghhBBCCKEr+wTE0sQyACgAAA==`,
    74  			digest: digest.NewDigestFromHex(digest.SHA256.String(), "39f100ed000b187ba74b3132cc207c63ad1765adaeb783aa7f242f1f7b6f5ea2"),
    75  		},
    76  	}
    77  
    78  	root, err := ioutil.TempDir("", "umoci-TestUnpackManifestCustomLayer")
    79  	if err != nil {
    80  		t.Fatal(err)
    81  	}
    82  
    83  	// Create our image.
    84  	image := filepath.Join(root, "image")
    85  	if err := dir.Create(image); err != nil {
    86  		t.Fatal(err)
    87  	}
    88  	engine, err := dir.Open(image)
    89  	if err != nil {
    90  		t.Fatal(err)
    91  	}
    92  	engineExt := casext.NewEngine(engine)
    93  
    94  	// Set up the CAS and an image from the above layers.
    95  	var layerDigests []digest.Digest
    96  	var layerDescriptors []ispec.Descriptor
    97  	for _, layer := range layers {
    98  		var layerReader io.Reader
    99  
   100  		// Since we already have the digests we don't need to jump through the
   101  		// hoops of decompressing our already-compressed blobs above to get the
   102  		// DiffIDs.
   103  		layerReader = bytes.NewBuffer(mustDecodeString(layer.base64))
   104  		layerDigest, layerSize, err := engineExt.PutBlob(ctx, layerReader)
   105  		if err != nil {
   106  			t.Fatal(err)
   107  		}
   108  
   109  		layerDigests = append(layerDigests, layer.digest)
   110  		layerDescriptors = append(layerDescriptors, ispec.Descriptor{
   111  			MediaType: ispec.MediaTypeImageLayerGzip,
   112  			Digest:    layerDigest,
   113  			Size:      layerSize,
   114  		})
   115  	}
   116  
   117  	// Create the config.
   118  	config := ispec.Image{
   119  		OS: "linux",
   120  		RootFS: ispec.RootFS{
   121  			Type:    "layers",
   122  			DiffIDs: layerDigests,
   123  		},
   124  	}
   125  	configDigest, configSize, err := engineExt.PutBlobJSON(ctx, config)
   126  	if err != nil {
   127  		t.Fatal(err)
   128  	}
   129  	configDescriptor := ispec.Descriptor{
   130  		MediaType: ispec.MediaTypeImageConfig,
   131  		Digest:    configDigest,
   132  		Size:      configSize,
   133  	}
   134  
   135  	// Create the manifest.
   136  	manifest := ispec.Manifest{
   137  		Versioned: specs.Versioned{
   138  			SchemaVersion: 2,
   139  		},
   140  		MediaType: ispec.MediaTypeImageManifest,
   141  		Config:    configDescriptor,
   142  		Layers:    layerDescriptors,
   143  	}
   144  
   145  	return root, manifest, engineExt
   146  }
   147  
   148  // Ensure that "custom layers" generated by other programs (such as a manual
   149  // tar+gzip) are still correctly handled by us (this used to not work because
   150  // that "archive/tar" parser doesn't consume the whole tar stream if it detects
   151  // that there is no more metadata it is interested in in the tar stream).
   152  func TestUnpackManifestCustomLayer(t *testing.T) {
   153  	ctx := context.Background()
   154  
   155  	root, manifest, engineExt := makeImage(t)
   156  	defer os.RemoveAll(root)
   157  
   158  	bundle, err := ioutil.TempDir("", "umoci-TestUnpackManifestCustomLayer_bundle")
   159  	if err != nil {
   160  		t.Fatal(err)
   161  	}
   162  	defer os.RemoveAll(bundle)
   163  
   164  	// Unpack (we map both root and the uid/gid in the archives to the current user).
   165  	unpackOptions := &UnpackOptions{MapOptions: MapOptions{
   166  		UIDMappings: []rspec.LinuxIDMapping{
   167  			{HostID: uint32(os.Geteuid()), ContainerID: 0, Size: 1},
   168  			{HostID: uint32(os.Geteuid()), ContainerID: 1000, Size: 1},
   169  		},
   170  		GIDMappings: []rspec.LinuxIDMapping{
   171  			{HostID: uint32(os.Getegid()), ContainerID: 0, Size: 1},
   172  			{HostID: uint32(os.Getegid()), ContainerID: 100, Size: 1},
   173  		},
   174  		Rootless: os.Geteuid() != 0,
   175  	}}
   176  	called := false
   177  	unpackOptions.AfterLayerUnpack = func(m ispec.Manifest, d ispec.Descriptor) error {
   178  		called = true
   179  		return nil
   180  	}
   181  	if err := UnpackManifest(ctx, engineExt, bundle, manifest, unpackOptions); err != nil {
   182  		t.Errorf("unexpected UnpackManifest error: %+v\n", err)
   183  	}
   184  	if !called {
   185  		t.Errorf("callback not called")
   186  	}
   187  }
   188  
   189  func TestUnpackStartFromDescriptor(t *testing.T) {
   190  	ctx := context.Background()
   191  
   192  	root, manifest, engineExt := makeImage(t)
   193  	defer os.RemoveAll(root)
   194  
   195  	bundle, err := ioutil.TempDir("", "umoci-TestUnpackStartFromDescriptor_bundle")
   196  	if err != nil {
   197  		t.Fatal(err)
   198  	}
   199  	defer os.RemoveAll(bundle)
   200  
   201  	// Unpack (we map both root and the uid/gid in the archives to the current user).
   202  	unpackOptions := &UnpackOptions{MapOptions: MapOptions{
   203  		UIDMappings: []rspec.LinuxIDMapping{
   204  			{HostID: uint32(os.Geteuid()), ContainerID: 0, Size: 1},
   205  			{HostID: uint32(os.Geteuid()), ContainerID: 1000, Size: 1},
   206  		},
   207  		GIDMappings: []rspec.LinuxIDMapping{
   208  			{HostID: uint32(os.Getegid()), ContainerID: 0, Size: 1},
   209  			{HostID: uint32(os.Getegid()), ContainerID: 100, Size: 1},
   210  		},
   211  		Rootless: os.Geteuid() != 0,
   212  	}}
   213  	unpackOptions.StartFrom = manifest.Layers[1]
   214  	if err := UnpackManifest(ctx, engineExt, bundle, manifest, unpackOptions); err != nil {
   215  		t.Errorf("unexpected UnpackManifest error: %+v\n", err)
   216  	}
   217  
   218  	_, err = os.Stat(filepath.Join(bundle, "rootfs/test_file"))
   219  	if err == nil || !os.IsNotExist(err) {
   220  		t.Errorf("test file present? %+v\n", err)
   221  	}
   222  }