Merge pull request #701 from stevvooe/initial-api-routes
Initial V2 API Router Implementationmaster
						commit
						0e8647f1ce
					
				|  | @ -0,0 +1,72 @@ | |||
| package registry | ||||
| 
 | ||||
| import ( | ||||
| 	"github.com/gorilla/mux" | ||||
| ) | ||||
| 
 | ||||
| const ( | ||||
| 	routeNameRoot             = "root" | ||||
| 	routeNameName             = "name" | ||||
| 	routeNameImageManifest    = "image-manifest" | ||||
| 	routeNameTags             = "tags" | ||||
| 	routeNameLayer            = "layer" | ||||
| 	routeNameStartLayerUpload = "start-layer-upload" | ||||
| 	routeNameLayerUpload      = "layer-upload" | ||||
| ) | ||||
| 
 | ||||
| var allEndpoints = []string{ | ||||
| 	routeNameImageManifest, | ||||
| 	routeNameTags, | ||||
| 	routeNameLayer, | ||||
| 	routeNameStartLayerUpload, | ||||
| 	routeNameLayerUpload, | ||||
| } | ||||
| 
 | ||||
| // v2APIRouter builds a gorilla router with named routes for the various API
 | ||||
| // methods. We may export this for use by the client.
 | ||||
| func v2APIRouter() *mux.Router { | ||||
| 	router := mux.NewRouter() | ||||
| 
 | ||||
| 	rootRouter := router. | ||||
| 		PathPrefix("/v2"). | ||||
| 		Name(routeNameRoot). | ||||
| 		Subrouter() | ||||
| 
 | ||||
| 	// All routes are subordinate to named routes
 | ||||
| 	namedRouter := rootRouter. | ||||
| 		PathPrefix("/{name:[A-Za-z0-9-_]+/[A-Za-z0-9-_]+}"). // TODO(stevvooe): Verify this format with core
 | ||||
| 		Name(routeNameName). | ||||
| 		Subrouter(). | ||||
| 		StrictSlash(true) | ||||
| 
 | ||||
| 	// GET      /v2/<name>/image/<tag>	Image Manifest	Fetch the image manifest identified by name and tag.
 | ||||
| 	// PUT      /v2/<name>/image/<tag>	Image Manifest	Upload the image manifest identified by name and tag.
 | ||||
| 	// DELETE   /v2/<name>/image/<tag>	Image Manifest	Delete the image identified by name and tag.
 | ||||
| 	namedRouter. | ||||
| 		Path("/image/{tag:[A-Za-z0-9-_]+}"). | ||||
| 		Name(routeNameImageManifest) | ||||
| 
 | ||||
| 	// GET	/v2/<name>/tags	Tags	Fetch the tags under the repository identified by name.
 | ||||
| 	namedRouter. | ||||
| 		Path("/tags"). | ||||
| 		Name(routeNameTags) | ||||
| 
 | ||||
| 	// GET	/v2/<name>/layer/<tarsum>	Layer	Fetch the layer identified by tarsum.
 | ||||
| 	namedRouter. | ||||
| 		Path("/layer/{tarsum}"). | ||||
| 		Name(routeNameLayer) | ||||
| 
 | ||||
| 	// POST	/v2/<name>/layer/<tarsum>/upload/	Layer Upload	Initiate an upload of the layer identified by tarsum. Requires length and a checksum parameter.
 | ||||
| 	namedRouter. | ||||
| 		Path("/layer/{tarsum}/upload/"). | ||||
| 		Name(routeNameStartLayerUpload) | ||||
| 
 | ||||
| 	// GET	/v2/<name>/layer/<tarsum>/upload/<uuid>	Layer Upload	Get the status of the upload identified by tarsum and uuid.
 | ||||
| 	// PUT	/v2/<name>/layer/<tarsum>/upload/<uuid>	Layer Upload	Upload all or a chunk of the upload identified by tarsum and uuid.
 | ||||
| 	// DELETE	/v2/<name>/layer/<tarsum>/upload/<uuid>	Layer Upload	Cancel the upload identified by layer and uuid
 | ||||
| 	namedRouter. | ||||
| 		Path("/layer/{tarsum}/upload/{uuid}"). | ||||
| 		Name(routeNameLayerUpload) | ||||
| 
 | ||||
| 	return router | ||||
| } | ||||
|  | @ -0,0 +1,122 @@ | |||
| package registry | ||||
| 
 | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"net/http" | ||||
| 	"net/http/httptest" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| 
 | ||||
| 	"github.com/gorilla/mux" | ||||
| ) | ||||
| 
 | ||||
| type routeInfo struct { | ||||
| 	RequestURI string | ||||
| 	Vars       map[string]string | ||||
| } | ||||
| 
 | ||||
| // TestRouter registers a test handler with all the routes and ensures that
 | ||||
| // each route returns the expected path variables. Not method verification is
 | ||||
| // present. This not meant to be exhaustive but as check to ensure that the
 | ||||
| // expected variables are extracted.
 | ||||
| //
 | ||||
| // This may go away as the application structure comes together.
 | ||||
| func TestRouter(t *testing.T) { | ||||
| 
 | ||||
| 	router := v2APIRouter() | ||||
| 
 | ||||
| 	testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||||
| 		routeInfo := routeInfo{ | ||||
| 			RequestURI: r.RequestURI, | ||||
| 			Vars:       mux.Vars(r), | ||||
| 		} | ||||
| 
 | ||||
| 		enc := json.NewEncoder(w) | ||||
| 
 | ||||
| 		if err := enc.Encode(routeInfo); err != nil { | ||||
| 			http.Error(w, err.Error(), http.StatusInternalServerError) | ||||
| 			return | ||||
| 		} | ||||
| 	}) | ||||
| 
 | ||||
| 	// Startup test server
 | ||||
| 	server := httptest.NewServer(router) | ||||
| 
 | ||||
| 	for _, testcase := range []struct { | ||||
| 		routeName         string | ||||
| 		expectedRouteInfo routeInfo | ||||
| 	}{ | ||||
| 		{ | ||||
| 			routeName: routeNameImageManifest, | ||||
| 			expectedRouteInfo: routeInfo{ | ||||
| 				RequestURI: "/v2/foo/bar/image/tag", | ||||
| 				Vars: map[string]string{ | ||||
| 					"name": "foo/bar", | ||||
| 					"tag":  "tag", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			routeName: routeNameTags, | ||||
| 			expectedRouteInfo: routeInfo{ | ||||
| 				RequestURI: "/v2/foo/bar/tags", | ||||
| 				Vars: map[string]string{ | ||||
| 					"name": "foo/bar", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			routeName: routeNameLayer, | ||||
| 			expectedRouteInfo: routeInfo{ | ||||
| 				RequestURI: "/v2/foo/bar/layer/tarsum", | ||||
| 				Vars: map[string]string{ | ||||
| 					"name":   "foo/bar", | ||||
| 					"tarsum": "tarsum", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			routeName: routeNameStartLayerUpload, | ||||
| 			expectedRouteInfo: routeInfo{ | ||||
| 				RequestURI: "/v2/foo/bar/layer/tarsum/upload/", | ||||
| 				Vars: map[string]string{ | ||||
| 					"name":   "foo/bar", | ||||
| 					"tarsum": "tarsum", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			routeName: routeNameLayerUpload, | ||||
| 			expectedRouteInfo: routeInfo{ | ||||
| 				RequestURI: "/v2/foo/bar/layer/tarsum/upload/uuid", | ||||
| 				Vars: map[string]string{ | ||||
| 					"name":   "foo/bar", | ||||
| 					"tarsum": "tarsum", | ||||
| 					"uuid":   "uuid", | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} { | ||||
| 		// Register the endpoint
 | ||||
| 		router.GetRoute(testcase.routeName).Handler(testHandler) | ||||
| 		u := server.URL + testcase.expectedRouteInfo.RequestURI | ||||
| 
 | ||||
| 		resp, err := http.Get(u) | ||||
| 
 | ||||
| 		if err != nil { | ||||
| 			t.Fatalf("error issuing get request: %v", err) | ||||
| 		} | ||||
| 
 | ||||
| 		dec := json.NewDecoder(resp.Body) | ||||
| 
 | ||||
| 		var actualRouteInfo routeInfo | ||||
| 		if err := dec.Decode(&actualRouteInfo); err != nil { | ||||
| 			t.Fatalf("error reading json response: %v", err) | ||||
| 		} | ||||
| 
 | ||||
| 		if !reflect.DeepEqual(actualRouteInfo, testcase.expectedRouteInfo) { | ||||
| 			t.Fatalf("actual does not equal expected: %v != %v", actualRouteInfo, testcase.expectedRouteInfo) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
		Loading…
	
		Reference in New Issue