github.com/apcera/util@v0.0.0-20180322191801-7a50bc84ee48/dockertest/v2/mock_v2_registry.go (about) 1 // Copyright 2015 Apcera Inc. All rights reserved. 2 3 package v2 4 5 import ( 6 "encoding/json" 7 "fmt" 8 "io" 9 "net/http" 10 "net/http/httptest" 11 "sync" 12 13 "github.com/gorilla/mux" 14 ) 15 16 var ( 17 testVerbose = false // Change to true in order to see HTTP requests in test output. 18 testHttpServer *httptest.Server 19 mu sync.Mutex 20 21 // skipAuth skips sending authorization challenges entirely. 22 skipAuth bool 23 24 // Note: currently does not support supplying signed manifests. 25 testImageManifests = map[string]string{ 26 "library/nats:latest": libraryNatsLatestManifest, 27 "library/foobar:latest": libraryFoobarLatestManifest, 28 } 29 ) 30 31 func RunMockRegistry() *httptest.Server { 32 mu.Lock() 33 defer mu.Unlock() 34 35 if testHttpServer != nil { 36 return testHttpServer 37 } 38 39 r := mux.NewRouter() 40 41 r.HandleFunc("/token", handlerToken).Methods("GET") 42 r.HandleFunc("/v2/", handlerSupport).Methods("GET") 43 r.HandleFunc("/v2/{repo:[^/]+}/{image_name:[^/]+}/manifests/{image_ref:[^/]+}", handlerImageManifest).Methods("GET") 44 r.HandleFunc("/v2/{repo:[^/]+}/{image_name:[^/]+}/blobs/{blob_ref:[^/]+}", handlerBlob).Methods("GET") 45 46 testHttpServer = httptest.NewServer(logHandler(r)) 47 return testHttpServer 48 } 49 50 // SetSkipAuth allows for configuring the mock registry to not send auth 51 // challenges. 52 func SetSkipAuth(enabled bool) { 53 mu.Lock() 54 defer mu.Unlock() 55 skipAuth = enabled 56 } 57 58 func logHandler(handler http.Handler) http.Handler { 59 if !testVerbose { 60 return handler 61 } 62 lh := func(w http.ResponseWriter, r *http.Request) { 63 fmt.Printf("%s \"%s %s\"\n", r.RemoteAddr, r.Method, r.URL) 64 handler.ServeHTTP(w, r) 65 } 66 return http.HandlerFunc(lh) 67 } 68 69 func writeResponse(w http.ResponseWriter, httpStatus int, payload interface{}) { 70 w.WriteHeader(httpStatus) 71 body, err := json.Marshal(payload) 72 if err != nil { 73 io.WriteString(w, err.Error()) 74 return 75 } 76 w.Write(body) 77 } 78 79 func checkAuth(w http.ResponseWriter, r *http.Request) bool { 80 header := w.Header() 81 header.Add("Docker-Distribution-API-Version", "registry/2.0") 82 83 if skipAuth { 84 return true 85 } 86 87 if (len(r.Header.Get("Authorization"))) > 0 { 88 return true 89 } 90 91 realm := fmt.Sprintf("http://%s/token", r.Host) 92 service := fmt.Sprintf("http://%s", r.Host) 93 94 // TODO: allow user to specify a scope on the request and have it respected? 95 w.Header().Add("WWW-Authenticate", fmt.Sprintf(`Bearer realm=%q,service=%q`, realm, service)) 96 writeResponse(w, http.StatusUnauthorized, "Bad auth") 97 return false 98 } 99 100 func handlerToken(w http.ResponseWriter, r *http.Request) { 101 // Token request requires service and scope; see: 102 // 103 // https://docs.docker.com/registry/spec/auth/token/ 104 // 105 // Expected form: `service=registry.docker.io&scope=repository:library/nats"` 106 query := r.URL.Query() 107 108 service := query.Get("service") 109 scope := query.Get("scope") 110 111 if service == "" { 112 writeResponse(w, http.StatusBadRequest, "bad request: service is required") 113 } else if scope == "" { 114 writeResponse(w, http.StatusBadRequest, "bad request: scope is required") 115 } 116 117 tokenResponse := `{"token": "Bearer someBearerToken"}` 118 119 io.WriteString(w, tokenResponse) 120 } 121 122 func handlerSupport(w http.ResponseWriter, r *http.Request) { 123 if !checkAuth(w, r) { 124 return 125 } 126 127 w.Write([]byte("v2 API supported!")) 128 } 129 130 func handlerImageManifest(w http.ResponseWriter, r *http.Request) { 131 if !checkAuth(w, r) { 132 return 133 } 134 135 vars := mux.Vars(r) 136 repo, exists := vars["repo"] 137 if !exists { 138 http.NotFound(w, r) 139 return 140 } 141 142 imageName, exists := vars["image_name"] 143 if !exists { 144 http.NotFound(w, r) 145 return 146 } 147 148 // Tag or digest. 149 imageRef, exists := vars["image_ref"] 150 if !exists { 151 http.NotFound(w, r) 152 return 153 } 154 155 manifest, exists := testImageManifests[fmt.Sprintf("%s/%s:%s", repo, imageName, imageRef)] 156 if !exists { 157 http.NotFound(w, r) 158 return 159 } 160 io.WriteString(w, manifest) 161 } 162 163 func handlerBlob(w http.ResponseWriter, r *http.Request) { 164 if !checkAuth(w, r) { 165 return 166 } 167 168 vars := mux.Vars(r) 169 _, exists := vars["repo"] 170 if !exists { 171 http.NotFound(w, r) 172 return 173 } 174 175 _, exists = vars["image_name"] 176 if !exists { 177 http.NotFound(w, r) 178 return 179 } 180 181 blobRef, exists := vars["blob_ref"] 182 if !exists { 183 http.NotFound(w, r) 184 return 185 } 186 187 // Just write back the blob reference; completely fake content, not even a tar. 188 io.WriteString(w, blobRef) 189 }