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