registry_mock_test.go 14 KB

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