github.com/skf/moby@v1.13.1/distribution/push_v2_test.go (about) 1 package distribution 2 3 import ( 4 "net/http" 5 "reflect" 6 "testing" 7 8 "github.com/docker/distribution" 9 "github.com/docker/distribution/context" 10 "github.com/docker/distribution/digest" 11 "github.com/docker/distribution/manifest/schema2" 12 distreference "github.com/docker/distribution/reference" 13 "github.com/docker/docker/distribution/metadata" 14 "github.com/docker/docker/layer" 15 "github.com/docker/docker/pkg/progress" 16 "github.com/docker/docker/reference" 17 ) 18 19 func TestGetRepositoryMountCandidates(t *testing.T) { 20 for _, tc := range []struct { 21 name string 22 hmacKey string 23 targetRepo string 24 maxCandidates int 25 metadata []metadata.V2Metadata 26 candidates []metadata.V2Metadata 27 }{ 28 { 29 name: "empty metadata", 30 targetRepo: "busybox", 31 maxCandidates: -1, 32 metadata: []metadata.V2Metadata{}, 33 candidates: []metadata.V2Metadata{}, 34 }, 35 { 36 name: "one item not matching", 37 targetRepo: "busybox", 38 maxCandidates: -1, 39 metadata: []metadata.V2Metadata{taggedMetadata("key", "dgst", "127.0.0.1/repo")}, 40 candidates: []metadata.V2Metadata{}, 41 }, 42 { 43 name: "one item matching", 44 targetRepo: "busybox", 45 maxCandidates: -1, 46 metadata: []metadata.V2Metadata{taggedMetadata("hash", "1", "hello-world")}, 47 candidates: []metadata.V2Metadata{taggedMetadata("hash", "1", "hello-world")}, 48 }, 49 { 50 name: "allow missing SourceRepository", 51 targetRepo: "busybox", 52 maxCandidates: -1, 53 metadata: []metadata.V2Metadata{ 54 {Digest: digest.Digest("1")}, 55 {Digest: digest.Digest("3")}, 56 {Digest: digest.Digest("2")}, 57 }, 58 candidates: []metadata.V2Metadata{}, 59 }, 60 { 61 name: "handle docker.io", 62 targetRepo: "user/app", 63 maxCandidates: -1, 64 metadata: []metadata.V2Metadata{ 65 {Digest: digest.Digest("1"), SourceRepository: "docker.io/user/foo"}, 66 {Digest: digest.Digest("3"), SourceRepository: "user/bar"}, 67 {Digest: digest.Digest("2"), SourceRepository: "app"}, 68 }, 69 candidates: []metadata.V2Metadata{ 70 {Digest: digest.Digest("3"), SourceRepository: "user/bar"}, 71 {Digest: digest.Digest("1"), SourceRepository: "docker.io/user/foo"}, 72 {Digest: digest.Digest("2"), SourceRepository: "app"}, 73 }, 74 }, 75 { 76 name: "sort more items", 77 hmacKey: "abcd", 78 targetRepo: "127.0.0.1/foo/bar", 79 maxCandidates: -1, 80 metadata: []metadata.V2Metadata{ 81 taggedMetadata("hash", "1", "hello-world"), 82 taggedMetadata("efgh", "2", "127.0.0.1/hello-world"), 83 taggedMetadata("abcd", "3", "busybox"), 84 taggedMetadata("hash", "4", "busybox"), 85 taggedMetadata("hash", "5", "127.0.0.1/foo"), 86 taggedMetadata("hash", "6", "127.0.0.1/bar"), 87 taggedMetadata("efgh", "7", "127.0.0.1/foo/bar"), 88 taggedMetadata("abcd", "8", "127.0.0.1/xyz"), 89 taggedMetadata("hash", "9", "127.0.0.1/foo/app"), 90 }, 91 candidates: []metadata.V2Metadata{ 92 // first by matching hash 93 taggedMetadata("abcd", "8", "127.0.0.1/xyz"), 94 // then by longest matching prefix 95 taggedMetadata("hash", "9", "127.0.0.1/foo/app"), 96 taggedMetadata("hash", "5", "127.0.0.1/foo"), 97 // sort the rest of the matching items in reversed order 98 taggedMetadata("hash", "6", "127.0.0.1/bar"), 99 taggedMetadata("efgh", "2", "127.0.0.1/hello-world"), 100 }, 101 }, 102 { 103 name: "limit max candidates", 104 hmacKey: "abcd", 105 targetRepo: "user/app", 106 maxCandidates: 3, 107 metadata: []metadata.V2Metadata{ 108 taggedMetadata("abcd", "1", "user/app1"), 109 taggedMetadata("abcd", "2", "user/app/base"), 110 taggedMetadata("hash", "3", "user/app"), 111 taggedMetadata("abcd", "4", "127.0.0.1/user/app"), 112 taggedMetadata("hash", "5", "user/foo"), 113 taggedMetadata("hash", "6", "app/bar"), 114 }, 115 candidates: []metadata.V2Metadata{ 116 // first by matching hash 117 taggedMetadata("abcd", "2", "user/app/base"), 118 taggedMetadata("abcd", "1", "user/app1"), 119 // then by longest matching prefix 120 taggedMetadata("hash", "3", "user/app"), 121 }, 122 }, 123 } { 124 repoInfo, err := reference.ParseNamed(tc.targetRepo) 125 if err != nil { 126 t.Fatalf("[%s] failed to parse reference name: %v", tc.name, err) 127 } 128 candidates := getRepositoryMountCandidates(repoInfo, []byte(tc.hmacKey), tc.maxCandidates, tc.metadata) 129 if len(candidates) != len(tc.candidates) { 130 t.Errorf("[%s] got unexpected number of candidates: %d != %d", tc.name, len(candidates), len(tc.candidates)) 131 } 132 for i := 0; i < len(candidates) && i < len(tc.candidates); i++ { 133 if !reflect.DeepEqual(candidates[i], tc.candidates[i]) { 134 t.Errorf("[%s] candidate %d does not match expected: %#+v != %#+v", tc.name, i, candidates[i], tc.candidates[i]) 135 } 136 } 137 for i := len(candidates); i < len(tc.candidates); i++ { 138 t.Errorf("[%s] missing expected candidate at position %d (%#+v)", tc.name, i, tc.candidates[i]) 139 } 140 for i := len(tc.candidates); i < len(candidates); i++ { 141 t.Errorf("[%s] got unexpected candidate at position %d (%#+v)", tc.name, i, candidates[i]) 142 } 143 } 144 } 145 146 func TestLayerAlreadyExists(t *testing.T) { 147 for _, tc := range []struct { 148 name string 149 metadata []metadata.V2Metadata 150 targetRepo string 151 hmacKey string 152 maxExistenceChecks int 153 checkOtherRepositories bool 154 remoteBlobs map[digest.Digest]distribution.Descriptor 155 remoteErrors map[digest.Digest]error 156 expectedDescriptor distribution.Descriptor 157 expectedExists bool 158 expectedError error 159 expectedRequests []string 160 expectedAdditions []metadata.V2Metadata 161 expectedRemovals []metadata.V2Metadata 162 }{ 163 { 164 name: "empty metadata", 165 targetRepo: "busybox", 166 maxExistenceChecks: 3, 167 checkOtherRepositories: true, 168 }, 169 { 170 name: "single not existent metadata", 171 targetRepo: "busybox", 172 metadata: []metadata.V2Metadata{{Digest: digest.Digest("pear"), SourceRepository: "docker.io/library/busybox"}}, 173 maxExistenceChecks: 3, 174 expectedRequests: []string{"pear"}, 175 expectedRemovals: []metadata.V2Metadata{{Digest: digest.Digest("pear"), SourceRepository: "docker.io/library/busybox"}}, 176 }, 177 { 178 name: "access denied", 179 targetRepo: "busybox", 180 maxExistenceChecks: 1, 181 metadata: []metadata.V2Metadata{{Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/busybox"}}, 182 remoteErrors: map[digest.Digest]error{digest.Digest("apple"): distribution.ErrAccessDenied}, 183 expectedError: nil, 184 expectedRequests: []string{"apple"}, 185 }, 186 { 187 name: "not matching reposies", 188 targetRepo: "busybox", 189 maxExistenceChecks: 3, 190 metadata: []metadata.V2Metadata{ 191 {Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/hello-world"}, 192 {Digest: digest.Digest("orange"), SourceRepository: "docker.io/library/busybox/subapp"}, 193 {Digest: digest.Digest("pear"), SourceRepository: "docker.io/busybox"}, 194 {Digest: digest.Digest("plum"), SourceRepository: "busybox"}, 195 {Digest: digest.Digest("banana"), SourceRepository: "127.0.0.1/busybox"}, 196 }, 197 }, 198 { 199 name: "check other repositories", 200 targetRepo: "busybox", 201 maxExistenceChecks: 10, 202 checkOtherRepositories: true, 203 metadata: []metadata.V2Metadata{ 204 {Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/hello-world"}, 205 {Digest: digest.Digest("orange"), SourceRepository: "docker.io/library/busybox/subapp"}, 206 {Digest: digest.Digest("pear"), SourceRepository: "docker.io/busybox"}, 207 {Digest: digest.Digest("plum"), SourceRepository: "busybox"}, 208 {Digest: digest.Digest("banana"), SourceRepository: "127.0.0.1/busybox"}, 209 }, 210 expectedRequests: []string{"plum", "pear", "apple", "orange", "banana"}, 211 }, 212 { 213 name: "find existing blob", 214 targetRepo: "busybox", 215 metadata: []metadata.V2Metadata{{Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/busybox"}}, 216 maxExistenceChecks: 3, 217 remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("apple"): {Digest: digest.Digest("apple")}}, 218 expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("apple"), MediaType: schema2.MediaTypeLayer}, 219 expectedExists: true, 220 expectedRequests: []string{"apple"}, 221 }, 222 { 223 name: "find existing blob with different hmac", 224 targetRepo: "busybox", 225 metadata: []metadata.V2Metadata{{SourceRepository: "docker.io/library/busybox", Digest: digest.Digest("apple"), HMAC: "dummyhmac"}}, 226 maxExistenceChecks: 3, 227 remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("apple"): {Digest: digest.Digest("apple")}}, 228 expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("apple"), MediaType: schema2.MediaTypeLayer}, 229 expectedExists: true, 230 expectedRequests: []string{"apple"}, 231 expectedAdditions: []metadata.V2Metadata{{Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/busybox"}}, 232 }, 233 { 234 name: "overwrite media types", 235 targetRepo: "busybox", 236 metadata: []metadata.V2Metadata{{Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/busybox"}}, 237 hmacKey: "key", 238 maxExistenceChecks: 3, 239 remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("apple"): {Digest: digest.Digest("apple"), MediaType: "custom-media-type"}}, 240 expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("apple"), MediaType: schema2.MediaTypeLayer}, 241 expectedExists: true, 242 expectedRequests: []string{"apple"}, 243 expectedAdditions: []metadata.V2Metadata{taggedMetadata("key", "apple", "docker.io/library/busybox")}, 244 }, 245 { 246 name: "find existing blob among many", 247 targetRepo: "127.0.0.1/myapp", 248 hmacKey: "key", 249 metadata: []metadata.V2Metadata{ 250 taggedMetadata("someotherkey", "pear", "127.0.0.1/myapp"), 251 taggedMetadata("key", "apple", "127.0.0.1/myapp"), 252 taggedMetadata("", "plum", "127.0.0.1/myapp"), 253 }, 254 maxExistenceChecks: 3, 255 remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("pear"): {Digest: digest.Digest("pear")}}, 256 expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("pear"), MediaType: schema2.MediaTypeLayer}, 257 expectedExists: true, 258 expectedRequests: []string{"apple", "plum", "pear"}, 259 expectedAdditions: []metadata.V2Metadata{taggedMetadata("key", "pear", "127.0.0.1/myapp")}, 260 expectedRemovals: []metadata.V2Metadata{ 261 taggedMetadata("key", "apple", "127.0.0.1/myapp"), 262 {Digest: digest.Digest("plum"), SourceRepository: "127.0.0.1/myapp"}, 263 }, 264 }, 265 { 266 name: "reach maximum existence checks", 267 targetRepo: "user/app", 268 metadata: []metadata.V2Metadata{ 269 {Digest: digest.Digest("pear"), SourceRepository: "docker.io/user/app"}, 270 {Digest: digest.Digest("apple"), SourceRepository: "docker.io/user/app"}, 271 {Digest: digest.Digest("plum"), SourceRepository: "docker.io/user/app"}, 272 {Digest: digest.Digest("banana"), SourceRepository: "docker.io/user/app"}, 273 }, 274 maxExistenceChecks: 3, 275 remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("pear"): {Digest: digest.Digest("pear")}}, 276 expectedExists: false, 277 expectedRequests: []string{"banana", "plum", "apple"}, 278 expectedRemovals: []metadata.V2Metadata{ 279 {Digest: digest.Digest("banana"), SourceRepository: "docker.io/user/app"}, 280 {Digest: digest.Digest("plum"), SourceRepository: "docker.io/user/app"}, 281 {Digest: digest.Digest("apple"), SourceRepository: "docker.io/user/app"}, 282 }, 283 }, 284 { 285 name: "zero allowed existence checks", 286 targetRepo: "user/app", 287 metadata: []metadata.V2Metadata{ 288 {Digest: digest.Digest("pear"), SourceRepository: "docker.io/user/app"}, 289 {Digest: digest.Digest("apple"), SourceRepository: "docker.io/user/app"}, 290 {Digest: digest.Digest("plum"), SourceRepository: "docker.io/user/app"}, 291 {Digest: digest.Digest("banana"), SourceRepository: "docker.io/user/app"}, 292 }, 293 maxExistenceChecks: 0, 294 remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("pear"): {Digest: digest.Digest("pear")}}, 295 }, 296 { 297 name: "stat single digest just once", 298 targetRepo: "busybox", 299 metadata: []metadata.V2Metadata{ 300 taggedMetadata("key1", "pear", "docker.io/library/busybox"), 301 taggedMetadata("key2", "apple", "docker.io/library/busybox"), 302 taggedMetadata("key3", "apple", "docker.io/library/busybox"), 303 }, 304 maxExistenceChecks: 3, 305 remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("pear"): {Digest: digest.Digest("pear")}}, 306 expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("pear"), MediaType: schema2.MediaTypeLayer}, 307 expectedExists: true, 308 expectedRequests: []string{"apple", "pear"}, 309 expectedAdditions: []metadata.V2Metadata{{Digest: digest.Digest("pear"), SourceRepository: "docker.io/library/busybox"}}, 310 expectedRemovals: []metadata.V2Metadata{taggedMetadata("key3", "apple", "docker.io/library/busybox")}, 311 }, 312 { 313 name: "don't stop on first error", 314 targetRepo: "user/app", 315 hmacKey: "key", 316 metadata: []metadata.V2Metadata{ 317 taggedMetadata("key", "banana", "docker.io/user/app"), 318 taggedMetadata("key", "orange", "docker.io/user/app"), 319 taggedMetadata("key", "plum", "docker.io/user/app"), 320 }, 321 maxExistenceChecks: 3, 322 remoteErrors: map[digest.Digest]error{"orange": distribution.ErrAccessDenied}, 323 remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("apple"): {}}, 324 expectedError: nil, 325 expectedRequests: []string{"plum", "orange", "banana"}, 326 expectedRemovals: []metadata.V2Metadata{ 327 taggedMetadata("key", "plum", "docker.io/user/app"), 328 taggedMetadata("key", "banana", "docker.io/user/app"), 329 }, 330 }, 331 { 332 name: "remove outdated metadata", 333 targetRepo: "docker.io/user/app", 334 metadata: []metadata.V2Metadata{ 335 {Digest: digest.Digest("plum"), SourceRepository: "docker.io/library/busybox"}, 336 {Digest: digest.Digest("orange"), SourceRepository: "docker.io/user/app"}, 337 }, 338 maxExistenceChecks: 3, 339 remoteErrors: map[digest.Digest]error{"orange": distribution.ErrBlobUnknown}, 340 remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("plum"): {}}, 341 expectedExists: false, 342 expectedRequests: []string{"orange"}, 343 expectedRemovals: []metadata.V2Metadata{{Digest: digest.Digest("orange"), SourceRepository: "docker.io/user/app"}}, 344 }, 345 { 346 name: "missing SourceRepository", 347 targetRepo: "busybox", 348 metadata: []metadata.V2Metadata{ 349 {Digest: digest.Digest("1")}, 350 {Digest: digest.Digest("3")}, 351 {Digest: digest.Digest("2")}, 352 }, 353 maxExistenceChecks: 3, 354 expectedExists: false, 355 expectedRequests: []string{"2", "3", "1"}, 356 }, 357 358 { 359 name: "with and without SourceRepository", 360 targetRepo: "busybox", 361 metadata: []metadata.V2Metadata{ 362 {Digest: digest.Digest("1")}, 363 {Digest: digest.Digest("2"), SourceRepository: "docker.io/library/busybox"}, 364 {Digest: digest.Digest("3")}, 365 }, 366 remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("1"): {Digest: digest.Digest("1")}}, 367 maxExistenceChecks: 3, 368 expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("1"), MediaType: schema2.MediaTypeLayer}, 369 expectedExists: true, 370 expectedRequests: []string{"2", "3", "1"}, 371 expectedAdditions: []metadata.V2Metadata{{Digest: digest.Digest("1"), SourceRepository: "docker.io/library/busybox"}}, 372 expectedRemovals: []metadata.V2Metadata{ 373 {Digest: digest.Digest("2"), SourceRepository: "docker.io/library/busybox"}, 374 }, 375 }, 376 } { 377 repoInfo, err := reference.ParseNamed(tc.targetRepo) 378 if err != nil { 379 t.Fatalf("[%s] failed to parse reference name: %v", tc.name, err) 380 } 381 repo := &mockRepo{ 382 t: t, 383 errors: tc.remoteErrors, 384 blobs: tc.remoteBlobs, 385 requests: []string{}, 386 } 387 ctx := context.Background() 388 ms := &mockV2MetadataService{} 389 pd := &v2PushDescriptor{ 390 hmacKey: []byte(tc.hmacKey), 391 repoInfo: repoInfo, 392 layer: &storeLayer{ 393 Layer: layer.EmptyLayer, 394 }, 395 repo: repo, 396 v2MetadataService: ms, 397 pushState: &pushState{remoteLayers: make(map[layer.DiffID]distribution.Descriptor)}, 398 checkedDigests: make(map[digest.Digest]struct{}), 399 } 400 401 desc, exists, err := pd.layerAlreadyExists(ctx, &progressSink{t}, layer.EmptyLayer.DiffID(), tc.checkOtherRepositories, tc.maxExistenceChecks, tc.metadata) 402 403 if !reflect.DeepEqual(desc, tc.expectedDescriptor) { 404 t.Errorf("[%s] got unexpected descriptor: %#+v != %#+v", tc.name, desc, tc.expectedDescriptor) 405 } 406 if exists != tc.expectedExists { 407 t.Errorf("[%s] got unexpected exists: %t != %t", tc.name, exists, tc.expectedExists) 408 } 409 if !reflect.DeepEqual(err, tc.expectedError) { 410 t.Errorf("[%s] got unexpected error: %#+v != %#+v", tc.name, err, tc.expectedError) 411 } 412 413 if len(repo.requests) != len(tc.expectedRequests) { 414 t.Errorf("[%s] got unexpected number of requests: %d != %d", tc.name, len(repo.requests), len(tc.expectedRequests)) 415 } 416 for i := 0; i < len(repo.requests) && i < len(tc.expectedRequests); i++ { 417 if repo.requests[i] != tc.expectedRequests[i] { 418 t.Errorf("[%s] request %d does not match expected: %q != %q", tc.name, i, repo.requests[i], tc.expectedRequests[i]) 419 } 420 } 421 for i := len(repo.requests); i < len(tc.expectedRequests); i++ { 422 t.Errorf("[%s] missing expected request at position %d (%q)", tc.name, i, tc.expectedRequests[i]) 423 } 424 for i := len(tc.expectedRequests); i < len(repo.requests); i++ { 425 t.Errorf("[%s] got unexpected request at position %d (%q)", tc.name, i, repo.requests[i]) 426 } 427 428 if len(ms.added) != len(tc.expectedAdditions) { 429 t.Errorf("[%s] got unexpected number of additions: %d != %d", tc.name, len(ms.added), len(tc.expectedAdditions)) 430 } 431 for i := 0; i < len(ms.added) && i < len(tc.expectedAdditions); i++ { 432 if ms.added[i] != tc.expectedAdditions[i] { 433 t.Errorf("[%s] added metadata at %d does not match expected: %q != %q", tc.name, i, ms.added[i], tc.expectedAdditions[i]) 434 } 435 } 436 for i := len(ms.added); i < len(tc.expectedAdditions); i++ { 437 t.Errorf("[%s] missing expected addition at position %d (%q)", tc.name, i, tc.expectedAdditions[i]) 438 } 439 for i := len(tc.expectedAdditions); i < len(ms.added); i++ { 440 t.Errorf("[%s] unexpected metadata addition at position %d (%q)", tc.name, i, ms.added[i]) 441 } 442 443 if len(ms.removed) != len(tc.expectedRemovals) { 444 t.Errorf("[%s] got unexpected number of removals: %d != %d", tc.name, len(ms.removed), len(tc.expectedRemovals)) 445 } 446 for i := 0; i < len(ms.removed) && i < len(tc.expectedRemovals); i++ { 447 if ms.removed[i] != tc.expectedRemovals[i] { 448 t.Errorf("[%s] removed metadata at %d does not match expected: %q != %q", tc.name, i, ms.removed[i], tc.expectedRemovals[i]) 449 } 450 } 451 for i := len(ms.removed); i < len(tc.expectedRemovals); i++ { 452 t.Errorf("[%s] missing expected removal at position %d (%q)", tc.name, i, tc.expectedRemovals[i]) 453 } 454 for i := len(tc.expectedRemovals); i < len(ms.removed); i++ { 455 t.Errorf("[%s] removed unexpected metadata at position %d (%q)", tc.name, i, ms.removed[i]) 456 } 457 } 458 } 459 460 func taggedMetadata(key string, dgst string, sourceRepo string) metadata.V2Metadata { 461 meta := metadata.V2Metadata{ 462 Digest: digest.Digest(dgst), 463 SourceRepository: sourceRepo, 464 } 465 466 meta.HMAC = metadata.ComputeV2MetadataHMAC([]byte(key), &meta) 467 return meta 468 } 469 470 type mockRepo struct { 471 t *testing.T 472 errors map[digest.Digest]error 473 blobs map[digest.Digest]distribution.Descriptor 474 requests []string 475 } 476 477 var _ distribution.Repository = &mockRepo{} 478 479 func (m *mockRepo) Named() distreference.Named { 480 m.t.Fatalf("Named() not implemented") 481 return nil 482 } 483 func (m *mockRepo) Manifests(ctc context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) { 484 m.t.Fatalf("Manifests() not implemented") 485 return nil, nil 486 } 487 func (m *mockRepo) Tags(ctc context.Context) distribution.TagService { 488 m.t.Fatalf("Tags() not implemented") 489 return nil 490 } 491 func (m *mockRepo) Blobs(ctx context.Context) distribution.BlobStore { 492 return &mockBlobStore{ 493 repo: m, 494 } 495 } 496 497 type mockBlobStore struct { 498 repo *mockRepo 499 } 500 501 var _ distribution.BlobStore = &mockBlobStore{} 502 503 func (m *mockBlobStore) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) { 504 m.repo.requests = append(m.repo.requests, dgst.String()) 505 if err, exists := m.repo.errors[dgst]; exists { 506 return distribution.Descriptor{}, err 507 } 508 if desc, exists := m.repo.blobs[dgst]; exists { 509 return desc, nil 510 } 511 return distribution.Descriptor{}, distribution.ErrBlobUnknown 512 } 513 func (m *mockBlobStore) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) { 514 m.repo.t.Fatal("Get() not implemented") 515 return nil, nil 516 } 517 518 func (m *mockBlobStore) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) { 519 m.repo.t.Fatal("Open() not implemented") 520 return nil, nil 521 } 522 523 func (m *mockBlobStore) Put(ctx context.Context, mediaType string, p []byte) (distribution.Descriptor, error) { 524 m.repo.t.Fatal("Put() not implemented") 525 return distribution.Descriptor{}, nil 526 } 527 528 func (m *mockBlobStore) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) { 529 m.repo.t.Fatal("Create() not implemented") 530 return nil, nil 531 } 532 func (m *mockBlobStore) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) { 533 m.repo.t.Fatal("Resume() not implemented") 534 return nil, nil 535 } 536 func (m *mockBlobStore) Delete(ctx context.Context, dgst digest.Digest) error { 537 m.repo.t.Fatal("Delete() not implemented") 538 return nil 539 } 540 func (m *mockBlobStore) ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error { 541 m.repo.t.Fatalf("ServeBlob() not implemented") 542 return nil 543 } 544 545 type mockV2MetadataService struct { 546 added []metadata.V2Metadata 547 removed []metadata.V2Metadata 548 } 549 550 var _ metadata.V2MetadataService = &mockV2MetadataService{} 551 552 func (*mockV2MetadataService) GetMetadata(diffID layer.DiffID) ([]metadata.V2Metadata, error) { 553 return nil, nil 554 } 555 func (*mockV2MetadataService) GetDiffID(dgst digest.Digest) (layer.DiffID, error) { 556 return "", nil 557 } 558 func (m *mockV2MetadataService) Add(diffID layer.DiffID, metadata metadata.V2Metadata) error { 559 m.added = append(m.added, metadata) 560 return nil 561 } 562 func (m *mockV2MetadataService) TagAndAdd(diffID layer.DiffID, hmacKey []byte, meta metadata.V2Metadata) error { 563 meta.HMAC = metadata.ComputeV2MetadataHMAC(hmacKey, &meta) 564 m.Add(diffID, meta) 565 return nil 566 } 567 func (m *mockV2MetadataService) Remove(metadata metadata.V2Metadata) error { 568 m.removed = append(m.removed, metadata) 569 return nil 570 } 571 572 type progressSink struct { 573 t *testing.T 574 } 575 576 func (s *progressSink) WriteProgress(p progress.Progress) error { 577 s.t.Logf("progress update: %#+v", p) 578 return nil 579 }