github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/engine/registry/registry_mock_test.go (about) 1 package registry // import "github.com/docker/docker/registry" 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io" 8 "net" 9 "net/http" 10 "net/http/httptest" 11 "net/url" 12 "strconv" 13 "strings" 14 "testing" 15 "time" 16 17 "github.com/docker/distribution/reference" 18 registrytypes "github.com/docker/docker/api/types/registry" 19 "github.com/gorilla/mux" 20 21 "github.com/sirupsen/logrus" 22 ) 23 24 var ( 25 testHTTPServer *httptest.Server 26 testHTTPSServer *httptest.Server 27 testLayers = map[string]map[string]string{ 28 "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20": { 29 "json": `{"id":"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", 30 "comment":"test base image","created":"2013-03-23T12:53:11.10432-07:00", 31 "container_config":{"Hostname":"","User":"","Memory":0,"MemorySwap":0, 32 "CpuShares":0,"AttachStdin":false,"AttachStdout":false,"AttachStderr":false, 33 "Tty":false,"OpenStdin":false,"StdinOnce":false, 34 "Env":null,"Cmd":null,"Dns":null,"Image":"","Volumes":null, 35 "VolumesFrom":"","Entrypoint":null},"Size":424242}`, 36 "checksum_simple": "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37", 37 "checksum_tarsum": "tarsum+sha256:4409a0685741ca86d38df878ed6f8cbba4c99de5dc73cd71aef04be3bb70be7c", 38 "ancestry": `["77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20"]`, 39 "layer": string([]byte{ 40 0x1f, 0x8b, 0x08, 0x08, 0x0e, 0xb0, 0xee, 0x51, 0x02, 0x03, 0x6c, 0x61, 0x79, 0x65, 41 0x72, 0x2e, 0x74, 0x61, 0x72, 0x00, 0xed, 0xd2, 0x31, 0x0e, 0xc2, 0x30, 0x0c, 0x05, 42 0x50, 0xcf, 0x9c, 0xc2, 0x27, 0x48, 0xed, 0x38, 0x4e, 0xce, 0x13, 0x44, 0x2b, 0x66, 43 0x62, 0x24, 0x8e, 0x4f, 0xa0, 0x15, 0x63, 0xb6, 0x20, 0x21, 0xfc, 0x96, 0xbf, 0x78, 44 0xb0, 0xf5, 0x1d, 0x16, 0x98, 0x8e, 0x88, 0x8a, 0x2a, 0xbe, 0x33, 0xef, 0x49, 0x31, 45 0xed, 0x79, 0x40, 0x8e, 0x5c, 0x44, 0x85, 0x88, 0x33, 0x12, 0x73, 0x2c, 0x02, 0xa8, 46 0xf0, 0x05, 0xf7, 0x66, 0xf5, 0xd6, 0x57, 0x69, 0xd7, 0x7a, 0x19, 0xcd, 0xf5, 0xb1, 47 0x6d, 0x1b, 0x1f, 0xf9, 0xba, 0xe3, 0x93, 0x3f, 0x22, 0x2c, 0xb6, 0x36, 0x0b, 0xf6, 48 0xb0, 0xa9, 0xfd, 0xe7, 0x94, 0x46, 0xfd, 0xeb, 0xd1, 0x7f, 0x2c, 0xc4, 0xd2, 0xfb, 49 0x97, 0xfe, 0x02, 0x80, 0xe4, 0xfd, 0x4f, 0x77, 0xae, 0x6d, 0x3d, 0x81, 0x73, 0xce, 50 0xb9, 0x7f, 0xf3, 0x04, 0x41, 0xc1, 0xab, 0xc6, 0x00, 0x0a, 0x00, 0x00, 51 }), 52 }, 53 "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d": { 54 "json": `{"id":"42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", 55 "parent":"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", 56 "comment":"test base image","created":"2013-03-23T12:55:11.10432-07:00", 57 "container_config":{"Hostname":"","User":"","Memory":0,"MemorySwap":0, 58 "CpuShares":0,"AttachStdin":false,"AttachStdout":false,"AttachStderr":false, 59 "Tty":false,"OpenStdin":false,"StdinOnce":false, 60 "Env":null,"Cmd":null,"Dns":null,"Image":"","Volumes":null, 61 "VolumesFrom":"","Entrypoint":null},"Size":424242}`, 62 "checksum_simple": "sha256:bea7bf2e4bacd479344b737328db47b18880d09096e6674165533aa994f5e9f2", 63 "checksum_tarsum": "tarsum+sha256:68fdb56fb364f074eec2c9b3f85ca175329c4dcabc4a6a452b7272aa613a07a2", 64 "ancestry": `["42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", 65 "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20"]`, 66 "layer": string([]byte{ 67 0x1f, 0x8b, 0x08, 0x08, 0xbd, 0xb3, 0xee, 0x51, 0x02, 0x03, 0x6c, 0x61, 0x79, 0x65, 68 0x72, 0x2e, 0x74, 0x61, 0x72, 0x00, 0xed, 0xd1, 0x31, 0x0e, 0xc2, 0x30, 0x0c, 0x05, 69 0x50, 0xcf, 0x9c, 0xc2, 0x27, 0x48, 0x9d, 0x38, 0x8e, 0xcf, 0x53, 0x51, 0xaa, 0x56, 70 0xea, 0x44, 0x82, 0xc4, 0xf1, 0x09, 0xb4, 0xea, 0x98, 0x2d, 0x48, 0x08, 0xbf, 0xe5, 71 0x2f, 0x1e, 0xfc, 0xf5, 0xdd, 0x00, 0xdd, 0x11, 0x91, 0x8a, 0xe0, 0x27, 0xd3, 0x9e, 72 0x14, 0xe2, 0x9e, 0x07, 0xf4, 0xc1, 0x2b, 0x0b, 0xfb, 0xa4, 0x82, 0xe4, 0x3d, 0x93, 73 0x02, 0x0a, 0x7c, 0xc1, 0x23, 0x97, 0xf1, 0x5e, 0x5f, 0xc9, 0xcb, 0x38, 0xb5, 0xee, 74 0xea, 0xd9, 0x3c, 0xb7, 0x4b, 0xbe, 0x7b, 0x9c, 0xf9, 0x23, 0xdc, 0x50, 0x6e, 0xb9, 75 0xb8, 0xf2, 0x2c, 0x5d, 0xf7, 0x4f, 0x31, 0xb6, 0xf6, 0x4f, 0xc7, 0xfe, 0x41, 0x55, 76 0x63, 0xdd, 0x9f, 0x89, 0x09, 0x90, 0x6c, 0xff, 0xee, 0xae, 0xcb, 0xba, 0x4d, 0x17, 77 0x30, 0xc6, 0x18, 0xf3, 0x67, 0x5e, 0xc1, 0xed, 0x21, 0x5d, 0x00, 0x0a, 0x00, 0x00, 78 }), 79 }, 80 } 81 testRepositories = map[string]map[string]string{ 82 "foo42/bar": { 83 "latest": "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", 84 "test": "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", 85 }, 86 } 87 mockHosts = map[string][]net.IP{ 88 "": {net.ParseIP("0.0.0.0")}, 89 "localhost": {net.ParseIP("127.0.0.1"), net.ParseIP("::1")}, 90 "example.com": {net.ParseIP("42.42.42.42")}, 91 "other.com": {net.ParseIP("43.43.43.43")}, 92 } 93 ) 94 95 func init() { 96 r := mux.NewRouter() 97 98 // /v1/ 99 r.HandleFunc("/v1/_ping", handlerGetPing).Methods(http.MethodGet) 100 r.HandleFunc("/v1/images/{image_id:[^/]+}/{action:json|layer|ancestry}", handlerGetImage).Methods(http.MethodGet) 101 r.HandleFunc("/v1/images/{image_id:[^/]+}/{action:json|layer|checksum}", handlerPutImage).Methods(http.MethodPut) 102 r.HandleFunc("/v1/repositories/{repository:.+}/tags", handlerGetDeleteTags).Methods(http.MethodGet, http.MethodDelete) 103 r.HandleFunc("/v1/repositories/{repository:.+}/tags/{tag:.+}", handlerGetTag).Methods(http.MethodGet) 104 r.HandleFunc("/v1/repositories/{repository:.+}/tags/{tag:.+}", handlerPutTag).Methods(http.MethodPut) 105 r.HandleFunc("/v1/users{null:.*}", handlerUsers).Methods(http.MethodGet, http.MethodPost, http.MethodPut) 106 r.HandleFunc("/v1/repositories/{repository:.+}{action:/images|/}", handlerImages).Methods(http.MethodGet, http.MethodPut, http.MethodDelete) 107 r.HandleFunc("/v1/repositories/{repository:.+}/auth", handlerAuth).Methods(http.MethodPut) 108 r.HandleFunc("/v1/search", handlerSearch).Methods(http.MethodGet) 109 110 // /v2/ 111 r.HandleFunc("/v2/version", handlerGetPing).Methods(http.MethodGet) 112 113 testHTTPServer = httptest.NewServer(handlerAccessLog(r)) 114 testHTTPSServer = httptest.NewTLSServer(handlerAccessLog(r)) 115 116 // override net.LookupIP 117 lookupIP = func(host string) ([]net.IP, error) { 118 if host == "127.0.0.1" { 119 // I believe in future Go versions this will fail, so let's fix it later 120 return net.LookupIP(host) 121 } 122 for h, addrs := range mockHosts { 123 if host == h { 124 return addrs, nil 125 } 126 for _, addr := range addrs { 127 if addr.String() == host { 128 return []net.IP{addr}, nil 129 } 130 } 131 } 132 return nil, errors.New("lookup: no such host") 133 } 134 } 135 136 func handlerAccessLog(handler http.Handler) http.Handler { 137 logHandler := func(w http.ResponseWriter, r *http.Request) { 138 logrus.Debugf("%s \"%s %s\"", r.RemoteAddr, r.Method, r.URL) 139 handler.ServeHTTP(w, r) 140 } 141 return http.HandlerFunc(logHandler) 142 } 143 144 func makeURL(req string) string { 145 return testHTTPServer.URL + req 146 } 147 148 func makeHTTPSURL(req string) string { 149 return testHTTPSServer.URL + req 150 } 151 152 func makeIndex(req string) *registrytypes.IndexInfo { 153 index := ®istrytypes.IndexInfo{ 154 Name: makeURL(req), 155 } 156 return index 157 } 158 159 func makeHTTPSIndex(req string) *registrytypes.IndexInfo { 160 index := ®istrytypes.IndexInfo{ 161 Name: makeHTTPSURL(req), 162 } 163 return index 164 } 165 166 func makePublicIndex() *registrytypes.IndexInfo { 167 index := ®istrytypes.IndexInfo{ 168 Name: IndexServer, 169 Secure: true, 170 Official: true, 171 } 172 return index 173 } 174 175 func makeServiceConfig(mirrors []string, insecureRegistries []string) (*serviceConfig, error) { 176 options := ServiceOptions{ 177 Mirrors: mirrors, 178 InsecureRegistries: insecureRegistries, 179 } 180 181 return newServiceConfig(options) 182 } 183 184 func writeHeaders(w http.ResponseWriter) { 185 h := w.Header() 186 h.Add("Server", "docker-tests/mock") 187 h.Add("Expires", "-1") 188 h.Add("Content-Type", "application/json") 189 h.Add("Pragma", "no-cache") 190 h.Add("Cache-Control", "no-cache") 191 h.Add("X-Docker-Registry-Version", "0.0.0") 192 h.Add("X-Docker-Registry-Config", "mock") 193 } 194 195 func writeResponse(w http.ResponseWriter, message interface{}, code int) { 196 writeHeaders(w) 197 w.WriteHeader(code) 198 body, err := json.Marshal(message) 199 if err != nil { 200 io.WriteString(w, err.Error()) 201 return 202 } 203 w.Write(body) 204 } 205 206 func readJSON(r *http.Request, dest interface{}) error { 207 body, err := io.ReadAll(r.Body) 208 if err != nil { 209 return err 210 } 211 return json.Unmarshal(body, dest) 212 } 213 214 func apiError(w http.ResponseWriter, message string, code int) { 215 body := map[string]string{ 216 "error": message, 217 } 218 writeResponse(w, body, code) 219 } 220 221 func assertEqual(t *testing.T, a interface{}, b interface{}, message string) { 222 if a == b { 223 return 224 } 225 if len(message) == 0 { 226 message = fmt.Sprintf("%v != %v", a, b) 227 } 228 t.Fatal(message) 229 } 230 231 func assertNotEqual(t *testing.T, a interface{}, b interface{}, message string) { 232 if a != b { 233 return 234 } 235 if len(message) == 0 { 236 message = fmt.Sprintf("%v == %v", a, b) 237 } 238 t.Fatal(message) 239 } 240 241 // Similar to assertEqual, but does not stop test 242 func checkEqual(t *testing.T, a interface{}, b interface{}, messagePrefix string) { 243 if a == b { 244 return 245 } 246 message := fmt.Sprintf("%v != %v", a, b) 247 if len(messagePrefix) != 0 { 248 message = messagePrefix + ": " + message 249 } 250 t.Error(message) 251 } 252 253 // Similar to assertNotEqual, but does not stop test 254 func checkNotEqual(t *testing.T, a interface{}, b interface{}, messagePrefix string) { 255 if a != b { 256 return 257 } 258 message := fmt.Sprintf("%v == %v", a, b) 259 if len(messagePrefix) != 0 { 260 message = messagePrefix + ": " + message 261 } 262 t.Error(message) 263 } 264 265 func requiresAuth(w http.ResponseWriter, r *http.Request) bool { 266 writeCookie := func() { 267 value := fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano()) 268 cookie := &http.Cookie{Name: "session", Value: value, MaxAge: 3600} 269 http.SetCookie(w, cookie) 270 // FIXME(sam): this should be sent only on Index routes 271 value = fmt.Sprintf("FAKE-TOKEN-%d", time.Now().UnixNano()) 272 w.Header().Add("X-Docker-Token", value) 273 } 274 if len(r.Cookies()) > 0 { 275 writeCookie() 276 return true 277 } 278 if len(r.Header.Get("Authorization")) > 0 { 279 writeCookie() 280 return true 281 } 282 w.Header().Add("WWW-Authenticate", "token") 283 apiError(w, "Wrong auth", http.StatusUnauthorized) 284 return false 285 } 286 287 func handlerGetPing(w http.ResponseWriter, r *http.Request) { 288 writeResponse(w, true, http.StatusOK) 289 } 290 291 func handlerGetImage(w http.ResponseWriter, r *http.Request) { 292 if !requiresAuth(w, r) { 293 return 294 } 295 vars := mux.Vars(r) 296 layer, exists := testLayers[vars["image_id"]] 297 if !exists { 298 http.NotFound(w, r) 299 return 300 } 301 writeHeaders(w) 302 layerSize := len(layer["layer"]) 303 w.Header().Add("X-Docker-Size", strconv.Itoa(layerSize)) 304 io.WriteString(w, layer[vars["action"]]) 305 } 306 307 func handlerPutImage(w http.ResponseWriter, r *http.Request) { 308 if !requiresAuth(w, r) { 309 return 310 } 311 vars := mux.Vars(r) 312 imageID := vars["image_id"] 313 action := vars["action"] 314 layer, exists := testLayers[imageID] 315 if !exists { 316 if action != "json" { 317 http.NotFound(w, r) 318 return 319 } 320 layer = make(map[string]string) 321 testLayers[imageID] = layer 322 } 323 if checksum := r.Header.Get("X-Docker-Checksum"); checksum != "" { 324 if checksum != layer["checksum_simple"] && checksum != layer["checksum_tarsum"] { 325 apiError(w, "Wrong checksum", http.StatusBadRequest) 326 return 327 } 328 } 329 body, err := io.ReadAll(r.Body) 330 if err != nil { 331 apiError(w, fmt.Sprintf("Error: %s", err), http.StatusInternalServerError) 332 return 333 } 334 layer[action] = string(body) 335 writeResponse(w, true, http.StatusOK) 336 } 337 338 func handlerGetDeleteTags(w http.ResponseWriter, r *http.Request) { 339 if !requiresAuth(w, r) { 340 return 341 } 342 repositoryName, err := reference.WithName(mux.Vars(r)["repository"]) 343 if err != nil { 344 apiError(w, "Could not parse repository", http.StatusBadRequest) 345 return 346 } 347 tags, exists := testRepositories[repositoryName.String()] 348 if !exists { 349 apiError(w, "Repository not found", http.StatusNotFound) 350 return 351 } 352 if r.Method == http.MethodDelete { 353 delete(testRepositories, repositoryName.String()) 354 writeResponse(w, true, http.StatusOK) 355 return 356 } 357 writeResponse(w, tags, http.StatusOK) 358 } 359 360 func handlerGetTag(w http.ResponseWriter, r *http.Request) { 361 if !requiresAuth(w, r) { 362 return 363 } 364 vars := mux.Vars(r) 365 repositoryName, err := reference.WithName(vars["repository"]) 366 if err != nil { 367 apiError(w, "Could not parse repository", http.StatusBadRequest) 368 return 369 } 370 tagName := vars["tag"] 371 tags, exists := testRepositories[repositoryName.String()] 372 if !exists { 373 apiError(w, "Repository not found", http.StatusNotFound) 374 return 375 } 376 tag, exists := tags[tagName] 377 if !exists { 378 apiError(w, "Tag not found", http.StatusNotFound) 379 return 380 } 381 writeResponse(w, tag, http.StatusOK) 382 } 383 384 func handlerPutTag(w http.ResponseWriter, r *http.Request) { 385 if !requiresAuth(w, r) { 386 return 387 } 388 vars := mux.Vars(r) 389 repositoryName, err := reference.WithName(vars["repository"]) 390 if err != nil { 391 apiError(w, "Could not parse repository", http.StatusBadRequest) 392 return 393 } 394 tagName := vars["tag"] 395 tags, exists := testRepositories[repositoryName.String()] 396 if !exists { 397 tags = make(map[string]string) 398 testRepositories[repositoryName.String()] = tags 399 } 400 tagValue := "" 401 readJSON(r, tagValue) 402 tags[tagName] = tagValue 403 writeResponse(w, true, http.StatusOK) 404 } 405 406 func handlerUsers(w http.ResponseWriter, r *http.Request) { 407 code := http.StatusOK 408 if r.Method == http.MethodPost { 409 code = http.StatusCreated 410 } else if r.Method == http.MethodPut { 411 code = http.StatusNoContent 412 } 413 writeResponse(w, "", code) 414 } 415 416 func handlerImages(w http.ResponseWriter, r *http.Request) { 417 u, _ := url.Parse(testHTTPServer.URL) 418 w.Header().Add("X-Docker-Endpoints", fmt.Sprintf("%s , %s ", u.Host, "test.example.com")) 419 w.Header().Add("X-Docker-Token", fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano())) 420 if r.Method == http.MethodPut { 421 if strings.HasSuffix(r.URL.Path, "images") { 422 writeResponse(w, "", http.StatusNoContent) 423 return 424 } 425 writeResponse(w, "", http.StatusOK) 426 return 427 } 428 if r.Method == http.MethodDelete { 429 writeResponse(w, "", http.StatusNoContent) 430 return 431 } 432 var images []map[string]string 433 for imageID, layer := range testLayers { 434 image := make(map[string]string) 435 image["id"] = imageID 436 image["checksum"] = layer["checksum_tarsum"] 437 image["Tag"] = "latest" 438 images = append(images, image) 439 } 440 writeResponse(w, images, http.StatusOK) 441 } 442 443 func handlerAuth(w http.ResponseWriter, r *http.Request) { 444 writeResponse(w, "OK", http.StatusOK) 445 } 446 447 func handlerSearch(w http.ResponseWriter, r *http.Request) { 448 result := ®istrytypes.SearchResults{ 449 Query: "fakequery", 450 NumResults: 1, 451 Results: []registrytypes.SearchResult{{Name: "fakeimage", StarCount: 42}}, 452 } 453 writeResponse(w, result, http.StatusOK) 454 } 455 456 func TestPing(t *testing.T) { 457 res, err := http.Get(makeURL("/v1/_ping")) 458 if err != nil { 459 t.Fatal(err) 460 } 461 assertEqual(t, res.StatusCode, http.StatusOK, "") 462 assertEqual(t, res.Header.Get("X-Docker-Registry-Config"), "mock", 463 "This is not a Mocked Registry") 464 } 465 466 /* Uncomment this to test Mocked Registry locally with curl 467 * WARNING: Don't push on the repos uncommented, it'll block the tests 468 * 469 func TestWait(t *testing.T) { 470 logrus.Println("Test HTTP server ready and waiting:", testHTTPServer.URL) 471 c := make(chan int) 472 <-c 473 } 474 475 //*/