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 }