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