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 }