github.com/rawahars/moby@v24.0.4+incompatible/distribution/push_v2_test.go (about) 1 package distribution // import "github.com/docker/docker/distribution" 2 3 import ( 4 "context" 5 "net/http" 6 "net/url" 7 "reflect" 8 "testing" 9 10 "github.com/docker/distribution" 11 "github.com/docker/distribution/manifest/schema2" 12 "github.com/docker/distribution/reference" 13 "github.com/docker/distribution/registry/api/errcode" 14 "github.com/docker/docker/api/types/registry" 15 "github.com/docker/docker/distribution/metadata" 16 "github.com/docker/docker/layer" 17 "github.com/docker/docker/pkg/progress" 18 refstore "github.com/docker/docker/reference" 19 registrypkg "github.com/docker/docker/registry" 20 "github.com/opencontainers/go-digest" 21 ) 22 23 func TestGetRepositoryMountCandidates(t *testing.T) { 24 for _, tc := range []struct { 25 name string 26 hmacKey string 27 targetRepo string 28 maxCandidates int 29 metadata []metadata.V2Metadata 30 candidates []metadata.V2Metadata 31 }{ 32 { 33 name: "empty metadata", 34 targetRepo: "busybox", 35 maxCandidates: -1, 36 metadata: []metadata.V2Metadata{}, 37 candidates: []metadata.V2Metadata{}, 38 }, 39 { 40 name: "one item not matching", 41 targetRepo: "busybox", 42 maxCandidates: -1, 43 metadata: []metadata.V2Metadata{taggedMetadata("key", "dgst", "127.0.0.1/repo")}, 44 candidates: []metadata.V2Metadata{}, 45 }, 46 { 47 name: "one item matching", 48 targetRepo: "busybox", 49 maxCandidates: -1, 50 metadata: []metadata.V2Metadata{taggedMetadata("hash", "1", "docker.io/library/hello-world")}, 51 candidates: []metadata.V2Metadata{taggedMetadata("hash", "1", "docker.io/library/hello-world")}, 52 }, 53 { 54 name: "allow missing SourceRepository", 55 targetRepo: "busybox", 56 maxCandidates: -1, 57 metadata: []metadata.V2Metadata{ 58 {Digest: digest.Digest("1")}, 59 {Digest: digest.Digest("3")}, 60 {Digest: digest.Digest("2")}, 61 }, 62 candidates: []metadata.V2Metadata{}, 63 }, 64 { 65 name: "handle docker.io", 66 targetRepo: "user/app", 67 maxCandidates: -1, 68 metadata: []metadata.V2Metadata{ 69 {Digest: digest.Digest("1"), SourceRepository: "docker.io/user/foo"}, 70 {Digest: digest.Digest("3"), SourceRepository: "docker.io/user/bar"}, 71 {Digest: digest.Digest("2"), SourceRepository: "docker.io/library/app"}, 72 }, 73 candidates: []metadata.V2Metadata{ 74 {Digest: digest.Digest("3"), SourceRepository: "docker.io/user/bar"}, 75 {Digest: digest.Digest("1"), SourceRepository: "docker.io/user/foo"}, 76 {Digest: digest.Digest("2"), SourceRepository: "docker.io/library/app"}, 77 }, 78 }, 79 { 80 name: "sort more items", 81 hmacKey: "abcd", 82 targetRepo: "127.0.0.1/foo/bar", 83 maxCandidates: -1, 84 metadata: []metadata.V2Metadata{ 85 taggedMetadata("hash", "1", "docker.io/library/hello-world"), 86 taggedMetadata("efgh", "2", "127.0.0.1/hello-world"), 87 taggedMetadata("abcd", "3", "docker.io/library/busybox"), 88 taggedMetadata("hash", "4", "docker.io/library/busybox"), 89 taggedMetadata("hash", "5", "127.0.0.1/foo"), 90 taggedMetadata("hash", "6", "127.0.0.1/bar"), 91 taggedMetadata("efgh", "7", "127.0.0.1/foo/bar"), 92 taggedMetadata("abcd", "8", "127.0.0.1/xyz"), 93 taggedMetadata("hash", "9", "127.0.0.1/foo/app"), 94 }, 95 candidates: []metadata.V2Metadata{ 96 // first by matching hash 97 taggedMetadata("abcd", "8", "127.0.0.1/xyz"), 98 // then by longest matching prefix 99 taggedMetadata("hash", "9", "127.0.0.1/foo/app"), 100 taggedMetadata("hash", "5", "127.0.0.1/foo"), 101 // sort the rest of the matching items in reversed order 102 taggedMetadata("hash", "6", "127.0.0.1/bar"), 103 taggedMetadata("efgh", "2", "127.0.0.1/hello-world"), 104 }, 105 }, 106 { 107 name: "limit max candidates", 108 hmacKey: "abcd", 109 targetRepo: "user/app", 110 maxCandidates: 3, 111 metadata: []metadata.V2Metadata{ 112 taggedMetadata("abcd", "1", "docker.io/user/app1"), 113 taggedMetadata("abcd", "2", "docker.io/user/app/base"), 114 taggedMetadata("hash", "3", "docker.io/user/app"), 115 taggedMetadata("abcd", "4", "127.0.0.1/user/app"), 116 taggedMetadata("hash", "5", "docker.io/user/foo"), 117 taggedMetadata("hash", "6", "docker.io/app/bar"), 118 }, 119 candidates: []metadata.V2Metadata{ 120 // first by matching hash 121 taggedMetadata("abcd", "2", "docker.io/user/app/base"), 122 taggedMetadata("abcd", "1", "docker.io/user/app1"), 123 // then by longest matching prefix 124 // "docker.io/usr/app" is excluded since candidates must 125 // be from a different repository 126 taggedMetadata("hash", "5", "docker.io/user/foo"), 127 }, 128 }, 129 } { 130 repoInfo, err := reference.ParseNormalizedNamed(tc.targetRepo) 131 if err != nil { 132 t.Fatalf("[%s] failed to parse reference name: %v", tc.name, err) 133 } 134 candidates := getRepositoryMountCandidates(repoInfo, []byte(tc.hmacKey), tc.maxCandidates, tc.metadata) 135 if len(candidates) != len(tc.candidates) { 136 t.Errorf("[%s] got unexpected number of candidates: %d != %d", tc.name, len(candidates), len(tc.candidates)) 137 } 138 for i := 0; i < len(candidates) && i < len(tc.candidates); i++ { 139 if !reflect.DeepEqual(candidates[i], tc.candidates[i]) { 140 t.Errorf("[%s] candidate %d does not match expected: %#+v != %#+v", tc.name, i, candidates[i], tc.candidates[i]) 141 } 142 } 143 for i := len(candidates); i < len(tc.candidates); i++ { 144 t.Errorf("[%s] missing expected candidate at position %d (%#+v)", tc.name, i, tc.candidates[i]) 145 } 146 for i := len(tc.candidates); i < len(candidates); i++ { 147 t.Errorf("[%s] got unexpected candidate at position %d (%#+v)", tc.name, i, candidates[i]) 148 } 149 } 150 } 151 152 func TestLayerAlreadyExists(t *testing.T) { 153 for _, tc := range []struct { 154 name string 155 metadata []metadata.V2Metadata 156 targetRepo string 157 hmacKey string 158 maxExistenceChecks int 159 checkOtherRepositories bool 160 remoteBlobs map[digest.Digest]distribution.Descriptor 161 remoteErrors map[digest.Digest]error 162 expectedDescriptor distribution.Descriptor 163 expectedExists bool 164 expectedError error 165 expectedRequests []string 166 expectedAdditions []metadata.V2Metadata 167 expectedRemovals []metadata.V2Metadata 168 }{ 169 { 170 name: "empty metadata", 171 targetRepo: "busybox", 172 maxExistenceChecks: 3, 173 checkOtherRepositories: true, 174 }, 175 { 176 name: "single not existent metadata", 177 targetRepo: "busybox", 178 metadata: []metadata.V2Metadata{{Digest: digest.Digest("pear"), SourceRepository: "docker.io/library/busybox"}}, 179 maxExistenceChecks: 3, 180 expectedRequests: []string{"pear"}, 181 expectedRemovals: []metadata.V2Metadata{{Digest: digest.Digest("pear"), SourceRepository: "docker.io/library/busybox"}}, 182 }, 183 { 184 name: "access denied", 185 targetRepo: "busybox", 186 maxExistenceChecks: 1, 187 metadata: []metadata.V2Metadata{{Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/busybox"}}, 188 remoteErrors: map[digest.Digest]error{digest.Digest("apple"): distribution.ErrAccessDenied}, 189 expectedError: nil, 190 expectedRequests: []string{"apple"}, 191 }, 192 { 193 name: "not matching repositories", 194 targetRepo: "busybox", 195 maxExistenceChecks: 3, 196 metadata: []metadata.V2Metadata{ 197 {Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/hello-world"}, 198 {Digest: digest.Digest("orange"), SourceRepository: "docker.io/library/busybox/subapp"}, 199 {Digest: digest.Digest("pear"), SourceRepository: "docker.io/busybox"}, 200 {Digest: digest.Digest("plum"), SourceRepository: "busybox"}, 201 {Digest: digest.Digest("banana"), SourceRepository: "127.0.0.1/busybox"}, 202 }, 203 }, 204 { 205 name: "check other repositories", 206 targetRepo: "busybox", 207 maxExistenceChecks: 10, 208 checkOtherRepositories: true, 209 metadata: []metadata.V2Metadata{ 210 {Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/hello-world"}, 211 {Digest: digest.Digest("orange"), SourceRepository: "docker.io/busybox/subapp"}, 212 {Digest: digest.Digest("pear"), SourceRepository: "docker.io/busybox"}, 213 {Digest: digest.Digest("plum"), SourceRepository: "docker.io/library/busybox"}, 214 {Digest: digest.Digest("banana"), SourceRepository: "127.0.0.1/busybox"}, 215 }, 216 expectedRequests: []string{"plum", "apple", "pear", "orange", "banana"}, 217 expectedRemovals: []metadata.V2Metadata{ 218 {Digest: digest.Digest("plum"), SourceRepository: "docker.io/library/busybox"}, 219 }, 220 }, 221 { 222 name: "find existing blob", 223 targetRepo: "busybox", 224 metadata: []metadata.V2Metadata{{Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/busybox"}}, 225 maxExistenceChecks: 3, 226 remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("apple"): {Digest: digest.Digest("apple")}}, 227 expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("apple"), MediaType: schema2.MediaTypeLayer}, 228 expectedExists: true, 229 expectedRequests: []string{"apple"}, 230 }, 231 { 232 name: "find existing blob with different hmac", 233 targetRepo: "busybox", 234 metadata: []metadata.V2Metadata{{SourceRepository: "docker.io/library/busybox", Digest: digest.Digest("apple"), HMAC: "dummyhmac"}}, 235 maxExistenceChecks: 3, 236 remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("apple"): {Digest: digest.Digest("apple")}}, 237 expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("apple"), MediaType: schema2.MediaTypeLayer}, 238 expectedExists: true, 239 expectedRequests: []string{"apple"}, 240 expectedAdditions: []metadata.V2Metadata{{Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/busybox"}}, 241 }, 242 { 243 name: "overwrite media types", 244 targetRepo: "busybox", 245 metadata: []metadata.V2Metadata{{Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/busybox"}}, 246 hmacKey: "key", 247 maxExistenceChecks: 3, 248 remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("apple"): {Digest: digest.Digest("apple"), MediaType: "custom-media-type"}}, 249 expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("apple"), MediaType: schema2.MediaTypeLayer}, 250 expectedExists: true, 251 expectedRequests: []string{"apple"}, 252 expectedAdditions: []metadata.V2Metadata{taggedMetadata("key", "apple", "docker.io/library/busybox")}, 253 }, 254 { 255 name: "find existing blob among many", 256 targetRepo: "127.0.0.1/myapp", 257 hmacKey: "key", 258 metadata: []metadata.V2Metadata{ 259 taggedMetadata("someotherkey", "pear", "127.0.0.1/myapp"), 260 taggedMetadata("key", "apple", "127.0.0.1/myapp"), 261 taggedMetadata("", "plum", "127.0.0.1/myapp"), 262 }, 263 maxExistenceChecks: 3, 264 remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("pear"): {Digest: digest.Digest("pear")}}, 265 expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("pear"), MediaType: schema2.MediaTypeLayer}, 266 expectedExists: true, 267 expectedRequests: []string{"apple", "plum", "pear"}, 268 expectedAdditions: []metadata.V2Metadata{taggedMetadata("key", "pear", "127.0.0.1/myapp")}, 269 expectedRemovals: []metadata.V2Metadata{ 270 taggedMetadata("key", "apple", "127.0.0.1/myapp"), 271 {Digest: digest.Digest("plum"), SourceRepository: "127.0.0.1/myapp"}, 272 }, 273 }, 274 { 275 name: "reach maximum existence checks", 276 targetRepo: "user/app", 277 metadata: []metadata.V2Metadata{ 278 {Digest: digest.Digest("pear"), SourceRepository: "docker.io/user/app"}, 279 {Digest: digest.Digest("apple"), SourceRepository: "docker.io/user/app"}, 280 {Digest: digest.Digest("plum"), SourceRepository: "docker.io/user/app"}, 281 {Digest: digest.Digest("banana"), SourceRepository: "docker.io/user/app"}, 282 }, 283 maxExistenceChecks: 3, 284 remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("pear"): {Digest: digest.Digest("pear")}}, 285 expectedExists: false, 286 expectedRequests: []string{"banana", "plum", "apple"}, 287 expectedRemovals: []metadata.V2Metadata{ 288 {Digest: digest.Digest("banana"), SourceRepository: "docker.io/user/app"}, 289 {Digest: digest.Digest("plum"), SourceRepository: "docker.io/user/app"}, 290 {Digest: digest.Digest("apple"), SourceRepository: "docker.io/user/app"}, 291 }, 292 }, 293 { 294 name: "zero allowed existence checks", 295 targetRepo: "user/app", 296 metadata: []metadata.V2Metadata{ 297 {Digest: digest.Digest("pear"), SourceRepository: "docker.io/user/app"}, 298 {Digest: digest.Digest("apple"), SourceRepository: "docker.io/user/app"}, 299 {Digest: digest.Digest("plum"), SourceRepository: "docker.io/user/app"}, 300 {Digest: digest.Digest("banana"), SourceRepository: "docker.io/user/app"}, 301 }, 302 maxExistenceChecks: 0, 303 remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("pear"): {Digest: digest.Digest("pear")}}, 304 }, 305 { 306 name: "stat single digest just once", 307 targetRepo: "busybox", 308 metadata: []metadata.V2Metadata{ 309 taggedMetadata("key1", "pear", "docker.io/library/busybox"), 310 taggedMetadata("key2", "apple", "docker.io/library/busybox"), 311 taggedMetadata("key3", "apple", "docker.io/library/busybox"), 312 }, 313 maxExistenceChecks: 3, 314 remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("pear"): {Digest: digest.Digest("pear")}}, 315 expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("pear"), MediaType: schema2.MediaTypeLayer}, 316 expectedExists: true, 317 expectedRequests: []string{"apple", "pear"}, 318 expectedAdditions: []metadata.V2Metadata{{Digest: digest.Digest("pear"), SourceRepository: "docker.io/library/busybox"}}, 319 expectedRemovals: []metadata.V2Metadata{taggedMetadata("key3", "apple", "docker.io/library/busybox")}, 320 }, 321 { 322 name: "don't stop on first error", 323 targetRepo: "user/app", 324 hmacKey: "key", 325 metadata: []metadata.V2Metadata{ 326 taggedMetadata("key", "banana", "docker.io/user/app"), 327 taggedMetadata("key", "orange", "docker.io/user/app"), 328 taggedMetadata("key", "plum", "docker.io/user/app"), 329 }, 330 maxExistenceChecks: 3, 331 remoteErrors: map[digest.Digest]error{"orange": distribution.ErrAccessDenied}, 332 remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("apple"): {}}, 333 expectedError: nil, 334 expectedRequests: []string{"plum", "orange", "banana"}, 335 expectedRemovals: []metadata.V2Metadata{ 336 taggedMetadata("key", "plum", "docker.io/user/app"), 337 taggedMetadata("key", "banana", "docker.io/user/app"), 338 }, 339 }, 340 { 341 name: "remove outdated metadata", 342 targetRepo: "docker.io/user/app", 343 metadata: []metadata.V2Metadata{ 344 {Digest: digest.Digest("plum"), SourceRepository: "docker.io/library/busybox"}, 345 {Digest: digest.Digest("orange"), SourceRepository: "docker.io/user/app"}, 346 }, 347 maxExistenceChecks: 3, 348 remoteErrors: map[digest.Digest]error{"orange": distribution.ErrBlobUnknown}, 349 remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("plum"): {}}, 350 expectedExists: false, 351 expectedRequests: []string{"orange"}, 352 expectedRemovals: []metadata.V2Metadata{{Digest: digest.Digest("orange"), SourceRepository: "docker.io/user/app"}}, 353 }, 354 { 355 name: "missing SourceRepository", 356 targetRepo: "busybox", 357 metadata: []metadata.V2Metadata{ 358 {Digest: digest.Digest("1")}, 359 {Digest: digest.Digest("3")}, 360 {Digest: digest.Digest("2")}, 361 }, 362 maxExistenceChecks: 3, 363 expectedExists: false, 364 expectedRequests: []string{"2", "3", "1"}, 365 }, 366 367 { 368 name: "with and without SourceRepository", 369 targetRepo: "busybox", 370 metadata: []metadata.V2Metadata{ 371 {Digest: digest.Digest("1")}, 372 {Digest: digest.Digest("2"), SourceRepository: "docker.io/library/busybox"}, 373 {Digest: digest.Digest("3")}, 374 }, 375 remoteBlobs: map[digest.Digest]distribution.Descriptor{digest.Digest("1"): {Digest: digest.Digest("1")}}, 376 maxExistenceChecks: 3, 377 expectedDescriptor: distribution.Descriptor{Digest: digest.Digest("1"), MediaType: schema2.MediaTypeLayer}, 378 expectedExists: true, 379 expectedRequests: []string{"2", "3", "1"}, 380 expectedAdditions: []metadata.V2Metadata{{Digest: digest.Digest("1"), SourceRepository: "docker.io/library/busybox"}}, 381 expectedRemovals: []metadata.V2Metadata{ 382 {Digest: digest.Digest("2"), SourceRepository: "docker.io/library/busybox"}, 383 }, 384 }, 385 } { 386 repoInfo, err := reference.ParseNormalizedNamed(tc.targetRepo) 387 if err != nil { 388 t.Fatalf("[%s] failed to parse reference name: %v", tc.name, err) 389 } 390 repo := &mockRepo{ 391 t: t, 392 errors: tc.remoteErrors, 393 blobs: tc.remoteBlobs, 394 requests: []string{}, 395 } 396 ctx := context.Background() 397 ms := &mockV2MetadataService{} 398 pd := &pushDescriptor{ 399 hmacKey: []byte(tc.hmacKey), 400 repoInfo: repoInfo, 401 layer: &storeLayer{ 402 Layer: layer.EmptyLayer, 403 }, 404 repo: repo, 405 metadataService: ms, 406 pushState: &pushState{remoteLayers: make(map[layer.DiffID]distribution.Descriptor)}, 407 checkedDigests: make(map[digest.Digest]struct{}), 408 } 409 410 desc, exists, err := pd.layerAlreadyExists(ctx, &progressSink{t}, layer.EmptyLayer.DiffID(), tc.checkOtherRepositories, tc.maxExistenceChecks, tc.metadata) 411 412 if !reflect.DeepEqual(desc, tc.expectedDescriptor) { 413 t.Errorf("[%s] got unexpected descriptor: %#+v != %#+v", tc.name, desc, tc.expectedDescriptor) 414 } 415 if exists != tc.expectedExists { 416 t.Errorf("[%s] got unexpected exists: %t != %t", tc.name, exists, tc.expectedExists) 417 } 418 if !reflect.DeepEqual(err, tc.expectedError) { 419 t.Errorf("[%s] got unexpected error: %#+v != %#+v", tc.name, err, tc.expectedError) 420 } 421 422 if len(repo.requests) != len(tc.expectedRequests) { 423 t.Errorf("[%s] got unexpected number of requests: %d != %d", tc.name, len(repo.requests), len(tc.expectedRequests)) 424 } 425 for i := 0; i < len(repo.requests) && i < len(tc.expectedRequests); i++ { 426 if repo.requests[i] != tc.expectedRequests[i] { 427 t.Errorf("[%s] request %d does not match expected: %q != %q", tc.name, i, repo.requests[i], tc.expectedRequests[i]) 428 } 429 } 430 for i := len(repo.requests); i < len(tc.expectedRequests); i++ { 431 t.Errorf("[%s] missing expected request at position %d (%q)", tc.name, i, tc.expectedRequests[i]) 432 } 433 for i := len(tc.expectedRequests); i < len(repo.requests); i++ { 434 t.Errorf("[%s] got unexpected request at position %d (%q)", tc.name, i, repo.requests[i]) 435 } 436 437 if len(ms.added) != len(tc.expectedAdditions) { 438 t.Errorf("[%s] got unexpected number of additions: %d != %d", tc.name, len(ms.added), len(tc.expectedAdditions)) 439 } 440 for i := 0; i < len(ms.added) && i < len(tc.expectedAdditions); i++ { 441 if ms.added[i] != tc.expectedAdditions[i] { 442 t.Errorf("[%s] added metadata at %d does not match expected: %q != %q", tc.name, i, ms.added[i], tc.expectedAdditions[i]) 443 } 444 } 445 for i := len(ms.added); i < len(tc.expectedAdditions); i++ { 446 t.Errorf("[%s] missing expected addition at position %d (%q)", tc.name, i, tc.expectedAdditions[i]) 447 } 448 for i := len(tc.expectedAdditions); i < len(ms.added); i++ { 449 t.Errorf("[%s] unexpected metadata addition at position %d (%q)", tc.name, i, ms.added[i]) 450 } 451 452 if len(ms.removed) != len(tc.expectedRemovals) { 453 t.Errorf("[%s] got unexpected number of removals: %d != %d", tc.name, len(ms.removed), len(tc.expectedRemovals)) 454 } 455 for i := 0; i < len(ms.removed) && i < len(tc.expectedRemovals); i++ { 456 if ms.removed[i] != tc.expectedRemovals[i] { 457 t.Errorf("[%s] removed metadata at %d does not match expected: %q != %q", tc.name, i, ms.removed[i], tc.expectedRemovals[i]) 458 } 459 } 460 for i := len(ms.removed); i < len(tc.expectedRemovals); i++ { 461 t.Errorf("[%s] missing expected removal at position %d (%q)", tc.name, i, tc.expectedRemovals[i]) 462 } 463 for i := len(tc.expectedRemovals); i < len(ms.removed); i++ { 464 t.Errorf("[%s] removed unexpected metadata at position %d (%q)", tc.name, i, ms.removed[i]) 465 } 466 } 467 } 468 469 type mockReferenceStore struct { 470 } 471 472 func (s *mockReferenceStore) References(id digest.Digest) []reference.Named { 473 return []reference.Named{} 474 } 475 func (s *mockReferenceStore) ReferencesByName(ref reference.Named) []refstore.Association { 476 return []refstore.Association{} 477 } 478 func (s *mockReferenceStore) AddTag(ref reference.Named, id digest.Digest, force bool) error { 479 return nil 480 } 481 func (s *mockReferenceStore) AddDigest(ref reference.Canonical, id digest.Digest, force bool) error { 482 return nil 483 } 484 func (s *mockReferenceStore) Delete(ref reference.Named) (bool, error) { 485 return true, nil 486 } 487 func (s *mockReferenceStore) Get(ref reference.Named) (digest.Digest, error) { 488 return "", nil 489 } 490 491 func TestWhenEmptyAuthConfig(t *testing.T) { 492 for _, authInfo := range []struct { 493 username string 494 password string 495 registryToken string 496 expected bool 497 }{ 498 { 499 username: "", 500 password: "", 501 registryToken: "", 502 expected: false, 503 }, 504 { 505 username: "username", 506 password: "password", 507 registryToken: "", 508 expected: true, 509 }, 510 { 511 username: "", 512 password: "", 513 registryToken: "token", 514 expected: true, 515 }, 516 } { 517 imagePushConfig := &ImagePushConfig{} 518 imagePushConfig.AuthConfig = ®istry.AuthConfig{ 519 Username: authInfo.username, 520 Password: authInfo.password, 521 RegistryToken: authInfo.registryToken, 522 } 523 imagePushConfig.ReferenceStore = &mockReferenceStore{} 524 repoInfo, _ := reference.ParseNormalizedNamed("xujihui1985/test.img") 525 pusher := &pusher{ 526 config: imagePushConfig, 527 repoInfo: ®istrypkg.RepositoryInfo{ 528 Name: repoInfo, 529 }, 530 endpoint: registrypkg.APIEndpoint{ 531 URL: &url.URL{ 532 Scheme: "https", 533 Host: "index.docker.io", 534 }, 535 Version: registrypkg.APIVersion2, 536 TrimHostname: true, 537 }, 538 } 539 pusher.push(context.Background()) 540 if pusher.pushState.hasAuthInfo != authInfo.expected { 541 t.Errorf("hasAuthInfo does not match expected: %t != %t", authInfo.expected, pusher.pushState.hasAuthInfo) 542 } 543 } 544 } 545 546 type mockBlobStoreWithCreate struct { 547 mockBlobStore 548 repo *mockRepoWithBlob 549 } 550 551 func (blob *mockBlobStoreWithCreate) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) { 552 return nil, errcode.Errors([]error{errcode.ErrorCodeUnauthorized.WithMessage("unauthorized")}) 553 } 554 555 type mockRepoWithBlob struct { 556 mockRepo 557 } 558 559 func (m *mockRepoWithBlob) Blobs(ctx context.Context) distribution.BlobStore { 560 blob := &mockBlobStoreWithCreate{} 561 blob.mockBlobStore.repo = &m.mockRepo 562 blob.repo = m 563 return blob 564 } 565 566 type mockMetadataService struct { 567 mockV2MetadataService 568 } 569 570 func (m *mockMetadataService) GetMetadata(diffID layer.DiffID) ([]metadata.V2Metadata, error) { 571 return []metadata.V2Metadata{ 572 taggedMetadata("abcd", "sha256:ff3a5c916c92643ff77519ffa742d3ec61b7f591b6b7504599d95a4a41134e28", "docker.io/user/app1"), 573 taggedMetadata("abcd", "sha256:ff3a5c916c92643ff77519ffa742d3ec61b7f591b6b7504599d95a4a41134e22", "docker.io/user/app/base"), 574 taggedMetadata("hash", "sha256:ff3a5c916c92643ff77519ffa742d3ec61b7f591b6b7504599d95a4a41134e23", "docker.io/user/app"), 575 taggedMetadata("abcd", "sha256:ff3a5c916c92643ff77519ffa742d3ec61b7f591b6b7504599d95a4a41134e24", "127.0.0.1/user/app"), 576 taggedMetadata("hash", "sha256:ff3a5c916c92643ff77519ffa742d3ec61b7f591b6b7504599d95a4a41134e25", "docker.io/user/foo"), 577 taggedMetadata("hash", "sha256:ff3a5c916c92643ff77519ffa742d3ec61b7f591b6b7504599d95a4a41134e26", "docker.io/app/bar"), 578 }, nil 579 } 580 581 var removeMetadata bool 582 583 func (m *mockMetadataService) Remove(metadata metadata.V2Metadata) error { 584 removeMetadata = true 585 return nil 586 } 587 588 func TestPushRegistryWhenAuthInfoEmpty(t *testing.T) { 589 repoInfo, _ := reference.ParseNormalizedNamed("user/app") 590 ms := &mockMetadataService{} 591 remoteErrors := map[digest.Digest]error{digest.Digest("sha256:apple"): distribution.ErrAccessDenied} 592 remoteBlobs := map[digest.Digest]distribution.Descriptor{digest.Digest("sha256:apple"): {Digest: digest.Digest("shar256:apple")}} 593 repo := &mockRepoWithBlob{ 594 mockRepo: mockRepo{ 595 t: t, 596 errors: remoteErrors, 597 blobs: remoteBlobs, 598 requests: []string{}, 599 }, 600 } 601 pd := &pushDescriptor{ 602 hmacKey: []byte("abcd"), 603 repoInfo: repoInfo, 604 layer: &storeLayer{ 605 Layer: layer.EmptyLayer, 606 }, 607 repo: repo, 608 metadataService: ms, 609 pushState: &pushState{ 610 remoteLayers: make(map[layer.DiffID]distribution.Descriptor), 611 hasAuthInfo: false, 612 }, 613 checkedDigests: make(map[digest.Digest]struct{}), 614 } 615 pd.Upload(context.Background(), &progressSink{t}) 616 if removeMetadata { 617 t.Fatalf("expect remove not be called but called") 618 } 619 } 620 621 func taggedMetadata(key string, dgst string, sourceRepo string) metadata.V2Metadata { 622 meta := metadata.V2Metadata{ 623 Digest: digest.Digest(dgst), 624 SourceRepository: sourceRepo, 625 } 626 627 meta.HMAC = metadata.ComputeV2MetadataHMAC([]byte(key), &meta) 628 return meta 629 } 630 631 type mockRepo struct { 632 t *testing.T 633 errors map[digest.Digest]error 634 blobs map[digest.Digest]distribution.Descriptor 635 requests []string 636 } 637 638 var _ distribution.Repository = &mockRepo{} 639 640 func (m *mockRepo) Named() reference.Named { 641 m.t.Fatalf("Named() not implemented") 642 return nil 643 } 644 func (m *mockRepo) Manifests(ctc context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) { 645 m.t.Fatalf("Manifests() not implemented") 646 return nil, nil 647 } 648 func (m *mockRepo) Tags(ctc context.Context) distribution.TagService { 649 m.t.Fatalf("Tags() not implemented") 650 return nil 651 } 652 func (m *mockRepo) Blobs(ctx context.Context) distribution.BlobStore { 653 return &mockBlobStore{ 654 repo: m, 655 } 656 } 657 658 type mockBlobStore struct { 659 repo *mockRepo 660 } 661 662 var _ distribution.BlobStore = &mockBlobStore{} 663 664 func (m *mockBlobStore) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) { 665 m.repo.requests = append(m.repo.requests, dgst.String()) 666 if err, exists := m.repo.errors[dgst]; exists { 667 return distribution.Descriptor{}, err 668 } 669 if desc, exists := m.repo.blobs[dgst]; exists { 670 return desc, nil 671 } 672 return distribution.Descriptor{}, distribution.ErrBlobUnknown 673 } 674 func (m *mockBlobStore) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) { 675 m.repo.t.Fatal("Get() not implemented") 676 return nil, nil 677 } 678 679 func (m *mockBlobStore) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) { 680 m.repo.t.Fatal("Open() not implemented") 681 return nil, nil 682 } 683 684 func (m *mockBlobStore) Put(ctx context.Context, mediaType string, p []byte) (distribution.Descriptor, error) { 685 m.repo.t.Fatal("Put() not implemented") 686 return distribution.Descriptor{}, nil 687 } 688 689 func (m *mockBlobStore) Create(ctx context.Context, options ...distribution.BlobCreateOption) (distribution.BlobWriter, error) { 690 m.repo.t.Fatal("Create() not implemented") 691 return nil, nil 692 } 693 func (m *mockBlobStore) Resume(ctx context.Context, id string) (distribution.BlobWriter, error) { 694 m.repo.t.Fatal("Resume() not implemented") 695 return nil, nil 696 } 697 func (m *mockBlobStore) Delete(ctx context.Context, dgst digest.Digest) error { 698 m.repo.t.Fatal("Delete() not implemented") 699 return nil 700 } 701 func (m *mockBlobStore) ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error { 702 m.repo.t.Fatalf("ServeBlob() not implemented") 703 return nil 704 } 705 706 type mockV2MetadataService struct { 707 added []metadata.V2Metadata 708 removed []metadata.V2Metadata 709 } 710 711 var _ metadata.V2MetadataService = &mockV2MetadataService{} 712 713 func (*mockV2MetadataService) GetMetadata(diffID layer.DiffID) ([]metadata.V2Metadata, error) { 714 return nil, nil 715 } 716 func (*mockV2MetadataService) GetDiffID(dgst digest.Digest) (layer.DiffID, error) { 717 return "", nil 718 } 719 func (m *mockV2MetadataService) Add(diffID layer.DiffID, metadata metadata.V2Metadata) error { 720 m.added = append(m.added, metadata) 721 return nil 722 } 723 func (m *mockV2MetadataService) TagAndAdd(diffID layer.DiffID, hmacKey []byte, meta metadata.V2Metadata) error { 724 meta.HMAC = metadata.ComputeV2MetadataHMAC(hmacKey, &meta) 725 m.Add(diffID, meta) 726 return nil 727 } 728 func (m *mockV2MetadataService) Remove(metadata metadata.V2Metadata) error { 729 m.removed = append(m.removed, metadata) 730 return nil 731 } 732 733 type progressSink struct { 734 t *testing.T 735 } 736 737 func (s *progressSink) WriteProgress(p progress.Progress) error { 738 s.t.Logf("progress update: %#+v", p) 739 return nil 740 }