registry_mock_test.go 11 KB

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