github.com/stchris/docker@v1.4.2-0.20150106053530-1510a324dbd5/registry/registry_mock_test.go (about) 1 package 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/gorilla/mux" 19 20 log "github.com/Sirupsen/logrus" 21 ) 22 23 var ( 24 testHTTPServer *httptest.Server 25 insecureRegistries []string 26 testLayers = map[string]map[string]string{ 27 "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20": { 28 "json": `{"id":"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", 29 "comment":"test base image","created":"2013-03-23T12:53:11.10432-07:00", 30 "container_config":{"Hostname":"","User":"","Memory":0,"MemorySwap":0, 31 "CpuShares":0,"AttachStdin":false,"AttachStdout":false,"AttachStderr":false, 32 "PortSpecs":null,"Tty":false,"OpenStdin":false,"StdinOnce":false, 33 "Env":null,"Cmd":null,"Dns":null,"Image":"","Volumes":null, 34 "VolumesFrom":"","Entrypoint":null},"Size":424242}`, 35 "checksum_simple": "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37", 36 "checksum_tarsum": "tarsum+sha256:4409a0685741ca86d38df878ed6f8cbba4c99de5dc73cd71aef04be3bb70be7c", 37 "ancestry": `["77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20"]`, 38 "layer": string([]byte{ 39 0x1f, 0x8b, 0x08, 0x08, 0x0e, 0xb0, 0xee, 0x51, 0x02, 0x03, 0x6c, 0x61, 0x79, 0x65, 40 0x72, 0x2e, 0x74, 0x61, 0x72, 0x00, 0xed, 0xd2, 0x31, 0x0e, 0xc2, 0x30, 0x0c, 0x05, 41 0x50, 0xcf, 0x9c, 0xc2, 0x27, 0x48, 0xed, 0x38, 0x4e, 0xce, 0x13, 0x44, 0x2b, 0x66, 42 0x62, 0x24, 0x8e, 0x4f, 0xa0, 0x15, 0x63, 0xb6, 0x20, 0x21, 0xfc, 0x96, 0xbf, 0x78, 43 0xb0, 0xf5, 0x1d, 0x16, 0x98, 0x8e, 0x88, 0x8a, 0x2a, 0xbe, 0x33, 0xef, 0x49, 0x31, 44 0xed, 0x79, 0x40, 0x8e, 0x5c, 0x44, 0x85, 0x88, 0x33, 0x12, 0x73, 0x2c, 0x02, 0xa8, 45 0xf0, 0x05, 0xf7, 0x66, 0xf5, 0xd6, 0x57, 0x69, 0xd7, 0x7a, 0x19, 0xcd, 0xf5, 0xb1, 46 0x6d, 0x1b, 0x1f, 0xf9, 0xba, 0xe3, 0x93, 0x3f, 0x22, 0x2c, 0xb6, 0x36, 0x0b, 0xf6, 47 0xb0, 0xa9, 0xfd, 0xe7, 0x94, 0x46, 0xfd, 0xeb, 0xd1, 0x7f, 0x2c, 0xc4, 0xd2, 0xfb, 48 0x97, 0xfe, 0x02, 0x80, 0xe4, 0xfd, 0x4f, 0x77, 0xae, 0x6d, 0x3d, 0x81, 0x73, 0xce, 49 0xb9, 0x7f, 0xf3, 0x04, 0x41, 0xc1, 0xab, 0xc6, 0x00, 0x0a, 0x00, 0x00, 50 }), 51 }, 52 "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d": { 53 "json": `{"id":"42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", 54 "parent":"77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20", 55 "comment":"test base image","created":"2013-03-23T12:55:11.10432-07:00", 56 "container_config":{"Hostname":"","User":"","Memory":0,"MemorySwap":0, 57 "CpuShares":0,"AttachStdin":false,"AttachStdout":false,"AttachStderr":false, 58 "PortSpecs":null,"Tty":false,"OpenStdin":false,"StdinOnce":false, 59 "Env":null,"Cmd":null,"Dns":null,"Image":"","Volumes":null, 60 "VolumesFrom":"","Entrypoint":null},"Size":424242}`, 61 "checksum_simple": "sha256:bea7bf2e4bacd479344b737328db47b18880d09096e6674165533aa994f5e9f2", 62 "checksum_tarsum": "tarsum+sha256:68fdb56fb364f074eec2c9b3f85ca175329c4dcabc4a6a452b7272aa613a07a2", 63 "ancestry": `["42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", 64 "77dbf71da1d00e3fbddc480176eac8994025630c6590d11cfc8fe1209c2a1d20"]`, 65 "layer": string([]byte{ 66 0x1f, 0x8b, 0x08, 0x08, 0xbd, 0xb3, 0xee, 0x51, 0x02, 0x03, 0x6c, 0x61, 0x79, 0x65, 67 0x72, 0x2e, 0x74, 0x61, 0x72, 0x00, 0xed, 0xd1, 0x31, 0x0e, 0xc2, 0x30, 0x0c, 0x05, 68 0x50, 0xcf, 0x9c, 0xc2, 0x27, 0x48, 0x9d, 0x38, 0x8e, 0xcf, 0x53, 0x51, 0xaa, 0x56, 69 0xea, 0x44, 0x82, 0xc4, 0xf1, 0x09, 0xb4, 0xea, 0x98, 0x2d, 0x48, 0x08, 0xbf, 0xe5, 70 0x2f, 0x1e, 0xfc, 0xf5, 0xdd, 0x00, 0xdd, 0x11, 0x91, 0x8a, 0xe0, 0x27, 0xd3, 0x9e, 71 0x14, 0xe2, 0x9e, 0x07, 0xf4, 0xc1, 0x2b, 0x0b, 0xfb, 0xa4, 0x82, 0xe4, 0x3d, 0x93, 72 0x02, 0x0a, 0x7c, 0xc1, 0x23, 0x97, 0xf1, 0x5e, 0x5f, 0xc9, 0xcb, 0x38, 0xb5, 0xee, 73 0xea, 0xd9, 0x3c, 0xb7, 0x4b, 0xbe, 0x7b, 0x9c, 0xf9, 0x23, 0xdc, 0x50, 0x6e, 0xb9, 74 0xb8, 0xf2, 0x2c, 0x5d, 0xf7, 0x4f, 0x31, 0xb6, 0xf6, 0x4f, 0xc7, 0xfe, 0x41, 0x55, 75 0x63, 0xdd, 0x9f, 0x89, 0x09, 0x90, 0x6c, 0xff, 0xee, 0xae, 0xcb, 0xba, 0x4d, 0x17, 76 0x30, 0xc6, 0x18, 0xf3, 0x67, 0x5e, 0xc1, 0xed, 0x21, 0x5d, 0x00, 0x0a, 0x00, 0x00, 77 }), 78 }, 79 } 80 testRepositories = map[string]map[string]string{ 81 "foo42/bar": { 82 "latest": "42d718c941f5c532ac049bf0b0ab53f0062f09a03afd4aa4a02c098e46032b9d", 83 }, 84 } 85 mockHosts = map[string][]net.IP{ 86 "": {net.ParseIP("0.0.0.0")}, 87 "localhost": {net.ParseIP("127.0.0.1"), net.ParseIP("::1")}, 88 "example.com": {net.ParseIP("42.42.42.42")}, 89 } 90 ) 91 92 func init() { 93 r := mux.NewRouter() 94 95 // /v1/ 96 r.HandleFunc("/v1/_ping", handlerGetPing).Methods("GET") 97 r.HandleFunc("/v1/images/{image_id:[^/]+}/{action:json|layer|ancestry}", handlerGetImage).Methods("GET") 98 r.HandleFunc("/v1/images/{image_id:[^/]+}/{action:json|layer|checksum}", handlerPutImage).Methods("PUT") 99 r.HandleFunc("/v1/repositories/{repository:.+}/tags", handlerGetDeleteTags).Methods("GET", "DELETE") 100 r.HandleFunc("/v1/repositories/{repository:.+}/tags/{tag:.+}", handlerGetTag).Methods("GET") 101 r.HandleFunc("/v1/repositories/{repository:.+}/tags/{tag:.+}", handlerPutTag).Methods("PUT") 102 r.HandleFunc("/v1/users{null:.*}", handlerUsers).Methods("GET", "POST", "PUT") 103 r.HandleFunc("/v1/repositories/{repository:.+}{action:/images|/}", handlerImages).Methods("GET", "PUT", "DELETE") 104 r.HandleFunc("/v1/repositories/{repository:.+}/auth", handlerAuth).Methods("PUT") 105 r.HandleFunc("/v1/search", handlerSearch).Methods("GET") 106 107 // /v2/ 108 r.HandleFunc("/v2/version", handlerGetPing).Methods("GET") 109 110 testHTTPServer = httptest.NewServer(handlerAccessLog(r)) 111 URL, err := url.Parse(testHTTPServer.URL) 112 if err != nil { 113 panic(err) 114 } 115 insecureRegistries = []string{URL.Host} 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 log.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 writeHeaders(w http.ResponseWriter) { 150 h := w.Header() 151 h.Add("Server", "docker-tests/mock") 152 h.Add("Expires", "-1") 153 h.Add("Content-Type", "application/json") 154 h.Add("Pragma", "no-cache") 155 h.Add("Cache-Control", "no-cache") 156 h.Add("X-Docker-Registry-Version", "0.0.0") 157 h.Add("X-Docker-Registry-Config", "mock") 158 } 159 160 func writeResponse(w http.ResponseWriter, message interface{}, code int) { 161 writeHeaders(w) 162 w.WriteHeader(code) 163 body, err := json.Marshal(message) 164 if err != nil { 165 io.WriteString(w, err.Error()) 166 return 167 } 168 w.Write(body) 169 } 170 171 func readJSON(r *http.Request, dest interface{}) error { 172 body, err := ioutil.ReadAll(r.Body) 173 if err != nil { 174 return err 175 } 176 return json.Unmarshal(body, dest) 177 } 178 179 func apiError(w http.ResponseWriter, message string, code int) { 180 body := map[string]string{ 181 "error": message, 182 } 183 writeResponse(w, body, code) 184 } 185 186 func assertEqual(t *testing.T, a interface{}, b interface{}, message string) { 187 if a == b { 188 return 189 } 190 if len(message) == 0 { 191 message = fmt.Sprintf("%v != %v", a, b) 192 } 193 t.Fatal(message) 194 } 195 196 func requiresAuth(w http.ResponseWriter, r *http.Request) bool { 197 writeCookie := func() { 198 value := fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano()) 199 cookie := &http.Cookie{Name: "session", Value: value, MaxAge: 3600} 200 http.SetCookie(w, cookie) 201 //FIXME(sam): this should be sent only on Index routes 202 value = fmt.Sprintf("FAKE-TOKEN-%d", time.Now().UnixNano()) 203 w.Header().Add("X-Docker-Token", value) 204 } 205 if len(r.Cookies()) > 0 { 206 writeCookie() 207 return true 208 } 209 if len(r.Header.Get("Authorization")) > 0 { 210 writeCookie() 211 return true 212 } 213 w.Header().Add("WWW-Authenticate", "token") 214 apiError(w, "Wrong auth", 401) 215 return false 216 } 217 218 func handlerGetPing(w http.ResponseWriter, r *http.Request) { 219 writeResponse(w, true, 200) 220 } 221 222 func handlerGetImage(w http.ResponseWriter, r *http.Request) { 223 if !requiresAuth(w, r) { 224 return 225 } 226 vars := mux.Vars(r) 227 layer, exists := testLayers[vars["image_id"]] 228 if !exists { 229 http.NotFound(w, r) 230 return 231 } 232 writeHeaders(w) 233 layerSize := len(layer["layer"]) 234 w.Header().Add("X-Docker-Size", strconv.Itoa(layerSize)) 235 io.WriteString(w, layer[vars["action"]]) 236 } 237 238 func handlerPutImage(w http.ResponseWriter, r *http.Request) { 239 if !requiresAuth(w, r) { 240 return 241 } 242 vars := mux.Vars(r) 243 imageID := vars["image_id"] 244 action := vars["action"] 245 layer, exists := testLayers[imageID] 246 if !exists { 247 if action != "json" { 248 http.NotFound(w, r) 249 return 250 } 251 layer = make(map[string]string) 252 testLayers[imageID] = layer 253 } 254 if checksum := r.Header.Get("X-Docker-Checksum"); checksum != "" { 255 if checksum != layer["checksum_simple"] && checksum != layer["checksum_tarsum"] { 256 apiError(w, "Wrong checksum", 400) 257 return 258 } 259 } 260 body, err := ioutil.ReadAll(r.Body) 261 if err != nil { 262 apiError(w, fmt.Sprintf("Error: %s", err), 500) 263 return 264 } 265 layer[action] = string(body) 266 writeResponse(w, true, 200) 267 } 268 269 func handlerGetDeleteTags(w http.ResponseWriter, r *http.Request) { 270 if !requiresAuth(w, r) { 271 return 272 } 273 repositoryName := mux.Vars(r)["repository"] 274 tags, exists := testRepositories[repositoryName] 275 if !exists { 276 apiError(w, "Repository not found", 404) 277 return 278 } 279 if r.Method == "DELETE" { 280 delete(testRepositories, repositoryName) 281 writeResponse(w, true, 200) 282 return 283 } 284 writeResponse(w, tags, 200) 285 } 286 287 func handlerGetTag(w http.ResponseWriter, r *http.Request) { 288 if !requiresAuth(w, r) { 289 return 290 } 291 vars := mux.Vars(r) 292 repositoryName := vars["repository"] 293 tagName := vars["tag"] 294 tags, exists := testRepositories[repositoryName] 295 if !exists { 296 apiError(w, "Repository not found", 404) 297 return 298 } 299 tag, exists := tags[tagName] 300 if !exists { 301 apiError(w, "Tag not found", 404) 302 return 303 } 304 writeResponse(w, tag, 200) 305 } 306 307 func handlerPutTag(w http.ResponseWriter, r *http.Request) { 308 if !requiresAuth(w, r) { 309 return 310 } 311 vars := mux.Vars(r) 312 repositoryName := vars["repository"] 313 tagName := vars["tag"] 314 tags, exists := testRepositories[repositoryName] 315 if !exists { 316 tags := make(map[string]string) 317 testRepositories[repositoryName] = tags 318 } 319 tagValue := "" 320 readJSON(r, tagValue) 321 tags[tagName] = tagValue 322 writeResponse(w, true, 200) 323 } 324 325 func handlerUsers(w http.ResponseWriter, r *http.Request) { 326 code := 200 327 if r.Method == "POST" { 328 code = 201 329 } else if r.Method == "PUT" { 330 code = 204 331 } 332 writeResponse(w, "", code) 333 } 334 335 func handlerImages(w http.ResponseWriter, r *http.Request) { 336 u, _ := url.Parse(testHTTPServer.URL) 337 w.Header().Add("X-Docker-Endpoints", fmt.Sprintf("%s , %s ", u.Host, "test.example.com")) 338 w.Header().Add("X-Docker-Token", fmt.Sprintf("FAKE-SESSION-%d", time.Now().UnixNano())) 339 if r.Method == "PUT" { 340 if strings.HasSuffix(r.URL.Path, "images") { 341 writeResponse(w, "", 204) 342 return 343 } 344 writeResponse(w, "", 200) 345 return 346 } 347 if r.Method == "DELETE" { 348 writeResponse(w, "", 204) 349 return 350 } 351 images := []map[string]string{} 352 for imageID, layer := range testLayers { 353 image := make(map[string]string) 354 image["id"] = imageID 355 image["checksum"] = layer["checksum_tarsum"] 356 image["Tag"] = "latest" 357 images = append(images, image) 358 } 359 writeResponse(w, images, 200) 360 } 361 362 func handlerAuth(w http.ResponseWriter, r *http.Request) { 363 writeResponse(w, "OK", 200) 364 } 365 366 func handlerSearch(w http.ResponseWriter, r *http.Request) { 367 result := &SearchResults{ 368 Query: "fakequery", 369 NumResults: 1, 370 Results: []SearchResult{{Name: "fakeimage", StarCount: 42}}, 371 } 372 writeResponse(w, result, 200) 373 } 374 375 func TestPing(t *testing.T) { 376 res, err := http.Get(makeURL("/v1/_ping")) 377 if err != nil { 378 t.Fatal(err) 379 } 380 assertEqual(t, res.StatusCode, 200, "") 381 assertEqual(t, res.Header.Get("X-Docker-Registry-Config"), "mock", 382 "This is not a Mocked Registry") 383 } 384 385 /* Uncomment this to test Mocked Registry locally with curl 386 * WARNING: Don't push on the repos uncommented, it'll block the tests 387 * 388 func TestWait(t *testing.T) { 389 log.Println("Test HTTP server ready and waiting:", testHttpServer.URL) 390 c := make(chan int) 391 <-c 392 } 393 394 //*/