diff --git a/api/datastore/mock.go b/api/datastore/mock.go index bb5919c6c..c8d9f03a7 100644 --- a/api/datastore/mock.go +++ b/api/datastore/mock.go @@ -56,8 +56,17 @@ func (m *Mock) GetRoutes(routeFilter *models.RouteFilter) ([]*models.Route, erro } func (m *Mock) GetRoutesByApp(appName string, routeFilter *models.RouteFilter) ([]*models.Route, error) { - // TODO: improve this mock method - return m.FakeRoutes, nil + var routes []*models.Route + route := m.FakeRoute + if route == nil && m.FakeRoutes != nil { + for _, r := range m.FakeRoutes { + if r.AppName == appName && r.Path == routeFilter.Path && r.AppName == routeFilter.AppName { + routes = append(routes, r) + } + } + } + + return routes, nil } func (m *Mock) StoreRoute(route *models.Route) (*models.Route, error) { diff --git a/api/models/route.go b/api/models/route.go index d090354f4..5ec7073ac 100644 --- a/api/models/route.go +++ b/api/models/route.go @@ -3,7 +3,9 @@ package models import ( "errors" "net/http" + "net/url" "path" + "strings" apiErrors "github.com/go-openapi/errors" ) @@ -32,13 +34,15 @@ type Route struct { } var ( - ErrRoutesValidationMissingName = errors.New("Missing route Name") - ErrRoutesValidationMissingImage = errors.New("Missing route Image") - ErrRoutesValidationMissingAppName = errors.New("Missing route AppName") - ErrRoutesValidationMissingPath = errors.New("Missing route Path") - ErrRoutesValidationInvalidPath = errors.New("Invalid Path format") - ErrRoutesValidationMissingType = errors.New("Missing route Type") - ErrRoutesValidationInvalidType = errors.New("Invalid route Type") + ErrRoutesValidationFoundDynamicURL = errors.New("Dynamic URL is not allowed") + ErrRoutesValidationInvalidPath = errors.New("Invalid Path format") + ErrRoutesValidationInvalidType = errors.New("Invalid route Type") + ErrRoutesValidationMissingAppName = errors.New("Missing route AppName") + ErrRoutesValidationMissingImage = errors.New("Missing route Image") + ErrRoutesValidationMissingName = errors.New("Missing route Name") + ErrRoutesValidationMissingPath = errors.New("Missing route Path") + ErrRoutesValidationMissingType = errors.New("Missing route Type") + ErrRoutesValidationPathMalformed = errors.New("Path malformed") ) func (r *Route) Validate() error { @@ -60,7 +64,16 @@ func (r *Route) Validate() error { res = append(res, ErrRoutesValidationMissingPath) } - if !path.IsAbs(r.Path) { + u, err := url.Parse(r.Path) + if err != nil { + res = append(res, ErrRoutesValidationPathMalformed) + } + + if strings.Contains(u.Path, ":") { + res = append(res, ErrRoutesValidationFoundDynamicURL) + } + + if !path.IsAbs(u.Path) { res = append(res, ErrRoutesValidationInvalidPath) } diff --git a/api/server/runner.go b/api/server/runner.go index 2468358a6..2395e9ad0 100644 --- a/api/server/runner.go +++ b/api/server/runner.go @@ -91,7 +91,7 @@ func handleRequest(c *gin.Context, enqueue models.Enqueue) { return } - routes, err := Api.Datastore.GetRoutesByApp(appName, &models.RouteFilter{}) + routes, err := Api.Datastore.GetRoutesByApp(appName, &models.RouteFilter{AppName: appName, Path: route}) if err != nil { log.WithError(err).Error(models.ErrRoutesList) c.JSON(http.StatusInternalServerError, simpleError(models.ErrRoutesList)) @@ -99,101 +99,104 @@ func handleRequest(c *gin.Context, enqueue models.Enqueue) { } log.WithField("routes", routes).Debug("Got routes from datastore") - for _, el := range routes { - log = log.WithFields(logrus.Fields{ - "app": appName, "route": el.Path, "image": el.Image}) - if params, match := matchRoute(el.Path, route); match { - - var stdout bytes.Buffer // TODO: should limit the size of this, error if gets too big. akin to: https://golang.org/pkg/io/#LimitReader - stderr := runner.NewFuncLogger(appName, route, el.Image, reqID) - - envVars := map[string]string{ - "METHOD": c.Request.Method, - "ROUTE": el.Path, - "REQUEST_URL": c.Request.URL.String(), - } - - // app config - for k, v := range app.Config { - envVars[toEnvName("CONFIG", k)] = v - } - - // route config - for k, v := range el.Config { - envVars[toEnvName("CONFIG", k)] = v - } - - // params - for _, param := range params { - envVars[toEnvName("PARAM", param.Key)] = param.Value - } - - // headers - for header, value := range c.Request.Header { - envVars[toEnvName("HEADER", header)] = strings.Join(value, " ") - } - - cfg := &runner.Config{ - Image: el.Image, - Timeout: 30 * time.Second, - ID: reqID, - AppName: appName, - Stdout: &stdout, - Stderr: stderr, - Env: envVars, - Memory: el.Memory, - Stdin: payload, - } - - var err error - var result drivers.RunResult - switch el.Type { - case "async": - // Read payload - pl, err := ioutil.ReadAll(cfg.Stdin) - if err != nil { - log.WithError(err).Error(models.ErrInvalidPayload) - c.JSON(http.StatusBadRequest, simpleError(models.ErrInvalidPayload)) - return - } - - // Create Task - priority := int32(0) - task := &models.Task{} - task.Image = &cfg.Image - task.ID = cfg.ID - task.Path = el.Path - task.AppName = cfg.AppName - task.Priority = &priority - task.EnvVars = cfg.Env - task.Payload = string(pl) - // Push to queue - enqueue(task) - log.Info("Added new task to queue") - - default: - if result, err = Api.Runner.Run(c, cfg); err != nil { - break - } - for k, v := range el.Headers { - c.Header(k, v[0]) - } - - if result.Status() == "success" { - c.Data(http.StatusOK, "", stdout.Bytes()) - } else { - - c.AbortWithStatus(http.StatusInternalServerError) - } - } - - return - } + if len(routes) == 0 { + log.WithError(err).Error(models.ErrRunnerRouteNotFound) + c.JSON(http.StatusNotFound, simpleError(models.ErrRunnerRouteNotFound)) + return } - log.WithError(err).Error(models.ErrRunnerRouteNotFound) - c.JSON(http.StatusNotFound, simpleError(models.ErrRunnerRouteNotFound)) + found := routes[0] + log = log.WithFields(logrus.Fields{ + "app": appName, "route": found.Path, "image": found.Image}) + + params, match := matchRoute(found.Path, route) + if !match { + log.WithError(err).Error(models.ErrRunnerRouteNotFound) + c.JSON(http.StatusNotFound, simpleError(models.ErrRunnerRouteNotFound)) + return + } + + var stdout bytes.Buffer // TODO: should limit the size of this, error if gets too big. akin to: https://golang.org/pkg/io/#LimitReader + stderr := runner.NewFuncLogger(appName, route, found.Image, reqID) + + envVars := map[string]string{ + "METHOD": c.Request.Method, + "ROUTE": found.Path, + "REQUEST_URL": c.Request.URL.String(), + } + + // app config + for k, v := range app.Config { + envVars[toEnvName("CONFIG", k)] = v + } + + // route config + for k, v := range found.Config { + envVars[toEnvName("CONFIG", k)] = v + } + + // params + for _, param := range params { + envVars[toEnvName("PARAM", param.Key)] = param.Value + } + + // headers + for header, value := range c.Request.Header { + envVars[toEnvName("HEADER", header)] = strings.Join(value, " ") + } + + cfg := &runner.Config{ + Image: found.Image, + Timeout: 30 * time.Second, + ID: reqID, + AppName: appName, + Stdout: &stdout, + Stderr: stderr, + Env: envVars, + Memory: found.Memory, + Stdin: payload, + } + + var result drivers.RunResult + switch found.Type { + case "async": + // Read payload + pl, err := ioutil.ReadAll(cfg.Stdin) + if err != nil { + log.WithError(err).Error(models.ErrInvalidPayload) + c.JSON(http.StatusBadRequest, simpleError(models.ErrInvalidPayload)) + return + } + + // Create Task + priority := int32(0) + task := &models.Task{} + task.Image = &cfg.Image + task.ID = cfg.ID + task.Path = found.Path + task.AppName = cfg.AppName + task.Priority = &priority + task.EnvVars = cfg.Env + task.Payload = string(pl) + // Push to queue + enqueue(task) + log.Info("Added new task to queue") + + default: + if result, err = Api.Runner.Run(c, cfg); err != nil { + break + } + for k, v := range found.Headers { + c.Header(k, v[0]) + } + + if result.Status() == "success" { + c.Data(http.StatusOK, "", stdout.Bytes()) + } else { + c.AbortWithStatus(http.StatusInternalServerError) + } + } } var fakeHandler = func(http.ResponseWriter, *http.Request, Params) {}