github.com/endophage/docker@v1.4.2-0.20161027011718-242853499895/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: distribution.ErrAccessDenied, 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: "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: distribution.ErrAccessDenied, 325 expectedRequests: []string{"plum", "orange"}, 326 expectedRemovals: []metadata.V2Metadata{taggedMetadata("key", "plum", "docker.io/user/app")}, 327 }, 328 { 329 name: "remove outdated metadata", 330 targetRepo: "docker.io/user/app", 331 metadata: []metadata.V2Metadata{ 332 {Digest: digest.Digest("plum"), SourceRepository: "docker.io/library/busybox"}, 333 {Digest: digest.Digest("orange"), SourceRepository: "docker.io/user/app"}, 334 }, 335 maxExistenceChecks: 3, 336 remoteErrors: map[digest.Digest]error{"orange": distribution.ErrBlobUnknown}, 337 remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("plum"): {}}, 338 expectedExists: false, 339 expectedRequests: []string{"orange"}, 340 expectedRemovals: []metadata.V2Metadata{{Digest: digest.Digest("orange"), SourceRepository: "docker.io/user/app"}}, 341 }, 342 { 343 name: "missing SourceRepository", 344 targetRepo: "busybox", 345 metadata: []metadata.V2Metadata{ 346 {Digest: digest.Digest("1")}, 347 {Digest: digest.Digest("3")}, 348 {Digest: digest.Digest("2")}, 349 }, 350 maxExistenceChecks: 3, 351 expectedExists: false, 352 expectedRequests: []string{"2", "3", "1"}, 353 }, 354 355 { 356 name: "with and without SourceRepository", 357 targetRepo: "busybox", 358 metadata: []metadata.V2Metadata{ 359 {Digest: digest.Digest("1")}, 360 {Digest: digest.Digest("2"), SourceRepository: "docker.io/library/busybox"}, 361 {Digest: digest.Digest("3")}, 362 }, 363 remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("1"): {Digest: digest.Digest("1")}}, 364 maxExistenceChecks: 3, 365 expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("1"), MediaType: schema2.MediaTypeLayer}, 366 expectedExists: true, 367 expectedRequests: []string{"2", "3", "1"}, 368 expectedAdditions: []metadata.V2Metadata{{Digest: digest.Digest("1"), SourceRepository: "docker.io/library/busybox"}}, 369 expectedRemovals: []metadata.V2Metadata{ 370 {Digest: digest.Digest("2"), SourceRepository: "docker.io/library/busybox"}, 371 }, 372 }, 373 } { 374 repoInfo, err := reference.ParseNamed(tc.targetRepo) 375 if err != nil { 376 t.Fatalf("[%s] failed to parse reference name: %v", tc.name, err) 377 } 378 repo := &mockRepo{ 379 t: t, 380 errors: tc.remoteErrors, 381 blobs: tc.remoteBlobs, 382 requests: []string{}, 383 } 384 ctx := context.Background() 385 ms := &mockV2MetadataService{} 386 pd := &v2PushDescriptor{ 387 hmacKey: []byte(tc.hmacKey), 388 repoInfo: repoInfo, 389 layer: layer.EmptyLayer, 390 repo: repo, 391 v2MetadataService: ms, 392 pushState: &pushState{remoteLayers: make(map[layer.DiffID]distribution.Descriptor)}, 393 checkedDigests: make(map[digest.Digest]struct{}), 394 } 395 396 desc, exists, err := pd.layerAlreadyExists(ctx, &progressSink{t}, layer.EmptyLayer.DiffID(), tc.checkOtherRepositories, tc.maxExistenceChecks, tc.metadata) 397 398 if !reflect.DeepEqual(desc, tc.expectedDescriptor) { 399 t.Errorf("[%s] got unexpected descriptor: %#+v != %#+v", tc.name, desc, tc.expectedDescriptor) 400 } 401 if exists != tc.expectedExists { 402 t.Errorf("[%s] got unexpected exists: %t != %t", tc.name, exists, tc.expectedExists) 403 } 404 if !reflect.DeepEqual(err, tc.expectedError) { 405 t.Errorf("[%s] got unexpected error: %#+v != %#+v", tc.name, err, tc.expectedError) 406 } 407 408 if len(repo.requests) != len(tc.expectedRequests) { 409 t.Errorf("[%s] got unexpected number of requests: %d != %d", tc.name, len(repo.requests), len(tc.expectedRequests)) 410 } 411 for i := 0; i < len(repo.requests) && i < len(tc.expectedRequests); i++ { 412 if repo.requests[i] != tc.expectedRequests[i] { 413 t.Errorf("[%s] request %d does not match expected: %q != %q", tc.name, i, repo.requests[i], tc.expectedRequests[i]) 414 } 415 } 416 for i := len(repo.requests); i < len(tc.expectedRequests); i++ { 417 t.Errorf("[%s] missing expected request at position %d (%q)", tc.name, i, tc.expectedRequests[i]) 418 } 419 for i := len(tc.expectedRequests); i < len(repo.requests); i++ { 420 t.Errorf("[%s] got unexpected request at position %d (%q)", tc.name, i, repo.requests[i]) 421 } 422 423 if len(ms.added) != len(tc.expectedAdditions) { 424 t.Errorf("[%s] got unexpected number of additions: %d != %d", tc.name, len(ms.added), len(tc.expectedAdditions)) 425 } 426 for i := 0; i < len(ms.added) && i < len(tc.expectedAdditions); i++ { 427 if ms.added[i] != tc.expectedAdditions[i] { 428 t.Errorf("[%s] added metadata at %d does not match expected: %q != %q", tc.name, i, ms.added[i], tc.expectedAdditions[i]) 429 } 430 } 431 for i := len(ms.added); i < len(tc.expectedAdditions); i++ { 432 t.Errorf("[%s] missing expected addition at position %d (%q)", tc.name, i, tc.expectedAdditions[i]) 433 } 434 for i := len(tc.expectedAdditions); i < len(ms.added); i++ { 435 t.Errorf("[%s] unexpected metadata addition at position %d (%q)", tc.name, i, ms.added[i]) 436 } 437 438 if len(ms.removed) != len(tc.expectedRemovals) { 439 t.Errorf("[%s] got unexpected number of removals: %d != %d", tc.name, len(ms.removed), len(tc.expectedRemovals)) 440 } 441 for i := 0; i < len(ms.removed) && i < len(tc.expectedRemovals); i++ { 442 if ms.removed[i] != tc.expectedRemovals[i] { 443 t.Errorf("[%s] removed metadata at %d does not match expected: %q != %q", tc.name, i, ms.removed[i], tc.expectedRemovals[i]) 444 } 445 } 446 for i := len(ms.removed); i < len(tc.expectedRemovals); i++ { 447 t.Errorf("[%s] missing expected removal at position %d (%q)", tc.name, i, tc.expectedRemovals[i]) 448 } 449 for i := len(tc.expectedRemovals); i < len(ms.removed); i++ { 450 t.Errorf("[%s] removed unexpected metadata at position %d (%q)", tc.name, i, ms.removed[i]) 451 } 452 } 453 } 454 455 func taggedMetadata(key string, dgst string, sourceRepo string) metadata.V2Metadata { 456 meta := metadata.V2Metadata{ 457 Digest: digest.Digest(dgst), 458 SourceRepository: sourceRepo, 459 } 460 461 meta.HMAC = metadata.ComputeV2MetadataHMAC([]byte(key), &meta) 462 return meta 463 } 464 465 type mockRepo struct { 466 t *testing.T 467 errors map[digest.Digest]error 468 blobs map[digest.Digest]distribution.Descriptor 469 requests []string 470 } 471 472 var _ distribution.Repository = &mockRepo{} 473 474 func (m *mockRepo) Named() distreference.Named { 475 m.t.Fatalf("Named() not implemented") 476 return nil 477 } 478 func (m *mockRepo) Manifests(ctc context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) { 479 m.t.Fatalf("Manifests() not implemented") 480 return nil, nil 481 } 482 func (m *mockRepo) Tags(ctc context.Context) distribution.TagService { 483 m.t.Fatalf("Tags() not implemented") 484 return nil 485 } 486 func (m *mockRepo) Blobs(ctx context.Context) distribution.BlobStore { 487 return &mockBlobStore{ 488 repo: m, 489 } 490 } 491 492 type mockBlobStore struct { 493 repo *mockRepo 494 } 495 496 var _ distribution.BlobStore = &mockBlobStore{} 497 498 func (m *mockBlobStore) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) { 499 m.repo.requests = append(m.repo.requests, dgst.String()) 500 if err, exists := m.repo.errors[dgst]; exists { 501 return distribution.Descriptor{}, err 502 } 503 if desc, exists := m.repo.blobs[dgst]; exists { 504 return desc, nil 505 } 506 return distribution.Descriptor{}, distribution.ErrBlobUnknown 507 } 508 func (m *mockBlobStore) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) { 509 m.repo.t.Fatal("Get() not implemented") 510 return nil, nil 511 } 512 513 func (m *mockBlobStore) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) { 514 m.repo.t.Fatal("Open() not implemented") 515 return nil, nil 516 } 517 518 func (m *mockBlobStore) Put(ctx context.Context, mediaType string, p []byte) (distribution.Descriptor, error) { 519 m.repo.t.Fatal("Put() not implemented") 520 return distribution.Descriptor{}, nil 521 } 522 523 func (m *mockBlobStore) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) { 524 m.repo.t.Fatal("Create() not implemented") 525 return nil, nil 526 } 527 func (m *mockBlobStore) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) { 528 m.repo.t.Fatal("Resume() not implemented") 529 return nil, nil 530 } 531 func (m *mockBlobStore) Delete(ctx context.Context, dgst digest.Digest) error { 532 m.repo.t.Fatal("Delete() not implemented") 533 return nil 534 } 535 func (m *mockBlobStore) ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error { 536 m.repo.t.Fatalf("ServeBlob() not implemented") 537 return nil 538 } 539 540 type mockV2MetadataService struct { 541 added []metadata.V2Metadata 542 removed []metadata.V2Metadata 543 } 544 545 var _ metadata.V2MetadataService = &mockV2MetadataService{} 546 547 func (*mockV2MetadataService) GetMetadata(diffID layer.DiffID) ([]metadata.V2Metadata, error) { 548 return nil, nil 549 } 550 func (*mockV2MetadataService) GetDiffID(dgst digest.Digest) (layer.DiffID, error) { 551 return "", nil 552 } 553 func (m *mockV2MetadataService) Add(diffID layer.DiffID, metadata metadata.V2Metadata) error { 554 m.added = append(m.added, metadata) 555 return nil 556 } 557 func (m *mockV2MetadataService) TagAndAdd(diffID layer.DiffID, hmacKey []byte, meta metadata.V2Metadata) error { 558 meta.HMAC = metadata.ComputeV2MetadataHMAC(hmacKey, &meta) 559 m.Add(diffID, meta) 560 return nil 561 } 562 func (m *mockV2MetadataService) Remove(metadata metadata.V2Metadata) error { 563 m.removed = append(m.removed, metadata) 564 return nil 565 } 566 567 type progressSink struct { 568 t *testing.T 569 } 570 571 func (s *progressSink) WriteProgress(p progress.Progress) error { 572 s.t.Logf("progress update: %#+v", p) 573 return nil 574 }