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