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