Adds before/after app get/list. And some bug fixes/cleanup. (#610)

* Adds before/after app get/list. And some bug fixes/cleanup.

* Fix test
This commit is contained in:
Travis Reeder
2017-12-21 09:32:03 -08:00
committed by GitHub
parent 0a1b180158
commit fdb4188146
26 changed files with 212 additions and 65 deletions

View File

@@ -362,7 +362,16 @@ func (ds *sqlStore) GetApp(ctx context.Context, name string) (*models.App, error
// GetApps retrieves an array of apps according to a specific filter. // GetApps retrieves an array of apps according to a specific filter.
func (ds *sqlStore) GetApps(ctx context.Context, filter *models.AppFilter) ([]*models.App, error) { func (ds *sqlStore) GetApps(ctx context.Context, filter *models.AppFilter) ([]*models.App, error) {
res := []*models.App{} res := []*models.App{}
query, args := buildFilterAppQuery(filter) if filter.NameIn != nil && len(filter.NameIn) == 0 { // this basically makes sure it doesn't return ALL apps
return res, nil
}
query, args, err := buildFilterAppQuery(filter)
if err != nil {
return nil, err
}
// fmt.Printf("QUERY: %v\n", query)
// fmt.Printf("ARGS: %v\n", args)
// hmm, should this have DISTINCT in it? shouldn't be possible to have two apps with same name
query = ds.db.Rebind(fmt.Sprintf("SELECT DISTINCT name, config FROM apps %s", query)) query = ds.db.Rebind(fmt.Sprintf("SELECT DISTINCT name, config FROM apps %s", query))
rows, err := ds.db.QueryxContext(ctx, query, args...) rows, err := ds.db.QueryxContext(ctx, query, args...)
if err != nil { if err != nil {
@@ -787,16 +796,29 @@ func buildFilterRouteQuery(filter *models.RouteFilter) (string, []interface{}) {
return b.String(), args return b.String(), args
} }
func buildFilterAppQuery(filter *models.AppFilter) (string, []interface{}) { func buildFilterAppQuery(filter *models.AppFilter) (string, []interface{}, error) {
var args []interface{}
if filter == nil { if filter == nil {
return "", nil return "", args, nil
} }
var b bytes.Buffer var b bytes.Buffer
var args []interface{}
where := func(colOp, val string) { // todo: this same thing is in several places in here, DRY it up across this file
if val != "" { where := func(colOp, val interface{}) {
if val == nil {
return
}
switch v := val.(type) {
case string:
if v == "" {
return
}
case []string:
if len(v) == 0 {
return
}
}
args = append(args, val) args = append(args, val)
if len(args) == 1 { if len(args) == 1 {
fmt.Fprintf(&b, `WHERE %s`, colOp) fmt.Fprintf(&b, `WHERE %s`, colOp)
@@ -804,16 +826,19 @@ func buildFilterAppQuery(filter *models.AppFilter) (string, []interface{}) {
fmt.Fprintf(&b, ` AND %s`, colOp) fmt.Fprintf(&b, ` AND %s`, colOp)
} }
} }
}
// where("name LIKE ?%", filter.Name) // TODO needs escaping? // where("name LIKE ?%", filter.Name) // TODO needs escaping?
where("name>?", filter.Cursor) where("name>?", filter.Cursor)
where("name IN (?)", filter.NameIn)
fmt.Fprintf(&b, ` ORDER BY name ASC`) // TODO assert this is indexed fmt.Fprintf(&b, ` ORDER BY name ASC`) // TODO assert this is indexed
fmt.Fprintf(&b, ` LIMIT ?`) fmt.Fprintf(&b, ` LIMIT ?`)
args = append(args, filter.PerPage) args = append(args, filter.PerPage)
if len(filter.NameIn) > 0 {
return b.String(), args // fmt.Println("about to sqlx.in:", b.String(), args)
return sqlx.In(b.String(), args...)
}
return b.String(), args, nil
} }
func buildFilterCallQuery(filter *models.CallFilter) (string, []interface{}) { func buildFilterCallQuery(filter *models.CallFilter) (string, []interface{}) {

View File

@@ -48,8 +48,11 @@ func (a *App) UpdateConfig(patch Config) {
} }
} }
// AppFilter is the filter used for querying apps
type AppFilter struct { type AppFilter struct {
Name string // prefix query TODO implemented Name string
// NameIn will filter by all names in the list (IN query)
NameIn []string
PerPage int PerPage int
Cursor string Cursor string
} }

View File

@@ -77,3 +77,50 @@ func (s *Server) FireAfterAppDelete(ctx context.Context, app *models.App) error
} }
return nil return nil
} }
// FireBeforeAppGet runs AppListener's BeforeAppGet method.
// todo: All of these listener methods could/should return the 2nd param rather than modifying in place. For instance,
// if a listener were to change the appName here (maybe prefix it or something for the database), it wouldn't be reflected anywhere else.
// If this returned appName, then keep passing along the returned appName, it would work.
func (s *Server) FireBeforeAppGet(ctx context.Context, appName string) error {
for _, l := range s.appListeners {
err := l.BeforeAppGet(ctx, appName)
if err != nil {
return err
}
}
return nil
}
// FireAfterAppGet runs AppListener's AfterAppGet method.
func (s *Server) FireAfterAppGet(ctx context.Context, app *models.App) error {
for _, l := range s.appListeners {
err := l.AfterAppGet(ctx, app)
if err != nil {
return err
}
}
return nil
}
// FireBeforeAppsList runs AppListener's BeforeAppsList method.
func (s *Server) FireBeforeAppsList(ctx context.Context, filter *models.AppFilter) error {
for _, l := range s.appListeners {
err := l.BeforeAppsList(ctx, filter)
if err != nil {
return err
}
}
return nil
}
// FireAfterAppsList runs AppListener's AfterAppsList method.
func (s *Server) FireAfterAppsList(ctx context.Context, apps []*models.App) error {
for _, l := range s.appListeners {
err := l.AfterAppsList(ctx, apps)
if err != nil {
return err
}
}
return nil
}

View File

@@ -34,7 +34,7 @@ func (s *Server) handleAppCreate(c *gin.Context) {
return return
} }
app, err := s.Datastore.InsertApp(ctx, wapp.App) app, err := s.Datastore().InsertApp(ctx, wapp.App)
if err != nil { if err != nil {
handleErrorResponse(c, err) handleErrorResponse(c, err)
return return

View File

@@ -17,7 +17,7 @@ func (s *Server) handleAppDelete(c *gin.Context) {
err := s.FireBeforeAppDelete(ctx, app) err := s.FireBeforeAppDelete(ctx, app)
err = s.Datastore.RemoveApp(ctx, app.Name) err = s.Datastore().RemoveApp(ctx, app.Name)
if err != nil { if err != nil {
handleErrorResponse(c, err) handleErrorResponse(c, err)
return return

View File

@@ -11,7 +11,19 @@ func (s *Server) handleAppGet(c *gin.Context) {
ctx := c.Request.Context() ctx := c.Request.Context()
appName := c.MustGet(api.AppName).(string) appName := c.MustGet(api.AppName).(string)
app, err := s.Datastore.GetApp(ctx, appName)
err := s.FireBeforeAppGet(ctx, appName)
if err != nil {
handleErrorResponse(c, err)
return
}
app, err := s.Datastore().GetApp(ctx, appName)
if err != nil {
handleErrorResponse(c, err)
return
}
err = s.FireAfterAppGet(ctx, app)
if err != nil { if err != nil {
handleErrorResponse(c, err) handleErrorResponse(c, err)
return return

View File

@@ -11,10 +11,21 @@ import (
func (s *Server) handleAppList(c *gin.Context) { func (s *Server) handleAppList(c *gin.Context) {
ctx := c.Request.Context() ctx := c.Request.Context()
var filter models.AppFilter filter := &models.AppFilter{}
filter.Cursor, filter.PerPage = pageParams(c, true) filter.Cursor, filter.PerPage = pageParams(c, true)
apps, err := s.Datastore.GetApps(ctx, &filter) err := s.FireBeforeAppsList(ctx, filter)
if err != nil {
handleErrorResponse(c, err)
return
}
apps, err := s.Datastore().GetApps(ctx, filter)
if err != nil {
handleErrorResponse(c, err)
return
}
err = s.FireAfterAppsList(ctx, apps)
if err != nil { if err != nil {
handleErrorResponse(c, err) handleErrorResponse(c, err)
return return

View File

@@ -31,13 +31,13 @@ func (s *Server) handleAppUpdate(c *gin.Context) {
wapp.App.Name = c.MustGet(api.AppName).(string) wapp.App.Name = c.MustGet(api.AppName).(string)
err = s.FireAfterAppUpdate(ctx, wapp.App) err = s.FireBeforeAppUpdate(ctx, wapp.App)
if err != nil { if err != nil {
handleErrorResponse(c, err) handleErrorResponse(c, err)
return return
} }
app, err := s.Datastore.UpdateApp(ctx, wapp.App) app, err := s.Datastore().UpdateApp(ctx, wapp.App)
if err != nil { if err != nil {
handleErrorResponse(c, err) handleErrorResponse(c, err)
return return

View File

@@ -12,7 +12,7 @@ func (s *Server) handleCallGet(c *gin.Context) {
appName := c.MustGet(api.AppName).(string) appName := c.MustGet(api.AppName).(string)
callID := c.Param(api.Call) callID := c.Param(api.Call)
callObj, err := s.Datastore.GetCall(ctx, appName, callID) callObj, err := s.Datastore().GetCall(ctx, appName, callID)
if err != nil { if err != nil {
handleErrorResponse(c, err) handleErrorResponse(c, err)
return return

View File

@@ -27,11 +27,11 @@ func (s *Server) handleCallList(c *gin.Context) {
return return
} }
calls, err := s.Datastore.GetCalls(ctx, &filter) calls, err := s.Datastore().GetCalls(ctx, &filter)
if len(calls) == 0 { if len(calls) == 0 {
// TODO this should be done in front of this handler to even get here... // TODO this should be done in front of this handler to even get here...
_, err = s.Datastore.GetApp(c, appName) _, err = s.Datastore().GetApp(c, appName)
} }
if err != nil { if err != nil {

View File

@@ -20,7 +20,7 @@ func (s *Server) apiAppHandlerWrapperFunc(apiHandler fnext.ApiAppHandler) gin.Ha
return func(c *gin.Context) { return func(c *gin.Context) {
// get the app // get the app
appName := c.Param(api.CApp) appName := c.Param(api.CApp)
app, err := s.Datastore.GetApp(c.Request.Context(), appName) app, err := s.Datastore().GetApp(c.Request.Context(), appName)
if err != nil { if err != nil {
handleErrorResponse(c, err) handleErrorResponse(c, err)
c.Abort() c.Abort()
@@ -41,7 +41,7 @@ func (s *Server) apiRouteHandlerWrapperFunc(apiHandler fnext.ApiRouteHandler) gi
context := c.Request.Context() context := c.Request.Context()
// get the app // get the app
appName := c.Param(api.CApp) appName := c.Param(api.CApp)
app, err := s.Datastore.GetApp(context, appName) app, err := s.Datastore().GetApp(context, appName)
if err != nil { if err != nil {
handleErrorResponse(c, err) handleErrorResponse(c, err)
c.Abort() c.Abort()
@@ -54,7 +54,7 @@ func (s *Server) apiRouteHandlerWrapperFunc(apiHandler fnext.ApiRouteHandler) gi
} }
// get the route TODO // get the route TODO
routePath := "/" + c.Param(api.CRoute) routePath := "/" + c.Param(api.CRoute)
route, err := s.Datastore.GetRoute(context, appName, routePath) route, err := s.Datastore().GetRoute(context, appName, routePath)
if err != nil { if err != nil {
handleErrorResponse(c, err) handleErrorResponse(c, err)
c.Abort() c.Abort()

View File

@@ -10,6 +10,7 @@ import (
"github.com/fnproject/fn/api" "github.com/fnproject/fn/api"
"github.com/fnproject/fn/api/common" "github.com/fnproject/fn/api/common"
"github.com/fnproject/fn/api/models" "github.com/fnproject/fn/api/models"
"github.com/fnproject/fn/fnext"
"github.com/gin-contrib/cors" "github.com/gin-contrib/cors"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
opentracing "github.com/opentracing/opentracing-go" opentracing "github.com/opentracing/opentracing-go"
@@ -85,7 +86,16 @@ func loggerWrap(c *gin.Context) {
c.Next() c.Next()
} }
func appWrap(c *gin.Context) { func setAppNameInCtx(c *gin.Context) {
// add appName to context
appName := c.GetString(api.AppName)
if appName != "" {
c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), fnext.AppNameKey, appName))
}
c.Next()
}
func appNameCheck(c *gin.Context) {
appName := c.GetString(api.AppName) appName := c.GetString(api.AppName)
if appName == "" { if appName == "" {
handleErrorResponse(c, models.ErrAppsMissingName) handleErrorResponse(c, models.ErrAppsMissingName)

View File

@@ -44,7 +44,7 @@ func (s *Server) handleRunnerEnqueue(c *gin.Context) {
// runner and enter into 'running' state before we can insert it in the db as // runner and enter into 'running' state before we can insert it in the db as
// 'queued' state. we can ignore any error inserting into db here and Start // 'queued' state. we can ignore any error inserting into db here and Start
// will ensure the call exists in the db in 'running' state there. // will ensure the call exists in the db in 'running' state there.
// s.Datastore.InsertCall(ctx, &call) // s.Datastore().InsertCall(ctx, &call)
c.JSON(200, struct { c.JSON(200, struct {
M string `json:"msg"` M string `json:"msg"`
@@ -112,7 +112,7 @@ func (s *Server) handleRunnerStart(c *gin.Context) {
// TODO do this client side and validate it here? // TODO do this client side and validate it here?
//call.Status = "running" //call.Status = "running"
//call.StartedAt = strfmt.DateTime(time.Now()) //call.StartedAt = strfmt.DateTime(time.Now())
//err := s.Datastore.UpdateCall(c.Request.Context(), &call) //err := s.Datastore().UpdateCall(c.Request.Context(), &call)
//if err != nil { //if err != nil {
//if err == InvalidStatusChange { //if err == InvalidStatusChange {
//// TODO we could either let UpdateCall handle setting to error or do it //// TODO we could either let UpdateCall handle setting to error or do it
@@ -153,7 +153,7 @@ func (s *Server) handleRunnerFinish(c *gin.Context) {
// TODO this needs UpdateCall functionality to work for async and should only work if: // TODO this needs UpdateCall functionality to work for async and should only work if:
// running->error|timeout|success // running->error|timeout|success
// TODO all async will fail here :( all sync will work fine :) -- *feeling conflicted* // TODO all async will fail here :( all sync will work fine :) -- *feeling conflicted*
if err := s.Datastore.InsertCall(ctx, &call); err != nil { if err := s.Datastore().InsertCall(ctx, &call); err != nil {
common.Logger(ctx).WithError(err).Error("error inserting call into datastore") common.Logger(ctx).WithError(err).Error("error inserting call into datastore")
// note: Not returning err here since the job could have already finished successfully. // note: Not returning err here since the job could have already finished successfully.
} }

View File

@@ -69,13 +69,15 @@ func (s *Server) runMiddleware(c *gin.Context, ms []fnext.Middleware) {
ctx := context.WithValue(c.Request.Context(), fnext.MiddlewareControllerKey, s.newMiddlewareController(c)) ctx := context.WithValue(c.Request.Context(), fnext.MiddlewareControllerKey, s.newMiddlewareController(c))
last := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { last := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// fmt.Println("final handler called") // fmt.Println("final handler called")
ctx := r.Context()
mctx := fnext.GetMiddlewareController(ctx)
// check for bypass // check for bypass
mctx := fnext.GetMiddlewareController(r.Context())
if mctx.FunctionCalled() { if mctx.FunctionCalled() {
// fmt.Println("func already called, skipping") // fmt.Println("func already called, skipping")
c.Abort() c.Abort()
return return
} }
c.Request = c.Request.WithContext(ctx)
c.Next() c.Next()
}) })

View File

@@ -57,7 +57,7 @@ func (s *Server) submitRoute(ctx context.Context, wroute *models.RouteWrapper) e
if err != nil { if err != nil {
return err return err
} }
r, err := s.Datastore.InsertRoute(ctx, wroute.Route) r, err := s.Datastore().InsertRoute(ctx, wroute.Route)
if err != nil { if err != nil {
return err return err
} }
@@ -66,7 +66,7 @@ func (s *Server) submitRoute(ctx context.Context, wroute *models.RouteWrapper) e
} }
func (s *Server) changeRoute(ctx context.Context, wroute *models.RouteWrapper) error { func (s *Server) changeRoute(ctx context.Context, wroute *models.RouteWrapper) error {
r, err := s.Datastore.UpdateRoute(ctx, wroute.Route) r, err := s.Datastore().UpdateRoute(ctx, wroute.Route)
if err != nil { if err != nil {
return err return err
} }
@@ -86,7 +86,7 @@ func (s *Server) ensureRoute(ctx context.Context, method string, wroute *models.
} }
return routeResponse{"Route successfully created", wroute.Route}, nil return routeResponse{"Route successfully created", wroute.Route}, nil
case http.MethodPut: case http.MethodPut:
_, err := s.Datastore.GetRoute(ctx, wroute.Route.AppName, wroute.Route.Path) _, err := s.Datastore().GetRoute(ctx, wroute.Route.AppName, wroute.Route.Path)
if err != nil && err == models.ErrRoutesNotFound { if err != nil && err == models.ErrRoutesNotFound {
err := s.submitRoute(ctx, wroute) err := s.submitRoute(ctx, wroute)
if err != nil { if err != nil {
@@ -111,7 +111,7 @@ func (s *Server) ensureRoute(ctx context.Context, method string, wroute *models.
// ensureApp will only execute if it is on post or put. Patch is not allowed to create apps. // ensureApp will only execute if it is on post or put. Patch is not allowed to create apps.
func (s *Server) ensureApp(ctx context.Context, wroute *models.RouteWrapper, method string) error { func (s *Server) ensureApp(ctx context.Context, wroute *models.RouteWrapper, method string) error {
app, err := s.Datastore.GetApp(ctx, wroute.Route.AppName) app, err := s.Datastore().GetApp(ctx, wroute.Route.AppName)
if err != nil && err != models.ErrAppsNotFound { if err != nil && err != models.ErrAppsNotFound {
return err return err
} else if app == nil { } else if app == nil {
@@ -126,7 +126,7 @@ func (s *Server) ensureApp(ctx context.Context, wroute *models.RouteWrapper, met
return err return err
} }
_, err = s.Datastore.InsertApp(ctx, newapp) _, err = s.Datastore().InsertApp(ctx, newapp)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -14,12 +14,12 @@ func (s *Server) handleRouteDelete(c *gin.Context) {
appName := c.MustGet(api.AppName).(string) appName := c.MustGet(api.AppName).(string)
routePath := path.Clean(c.MustGet(api.Path).(string)) routePath := path.Clean(c.MustGet(api.Path).(string))
if _, err := s.Datastore.GetRoute(ctx, appName, routePath); err != nil { if _, err := s.Datastore().GetRoute(ctx, appName, routePath); err != nil {
handleErrorResponse(c, err) handleErrorResponse(c, err)
return return
} }
if err := s.Datastore.RemoveRoute(ctx, appName, routePath); err != nil { if err := s.Datastore().RemoveRoute(ctx, appName, routePath); err != nil {
handleErrorResponse(c, err) handleErrorResponse(c, err)
return return
} }

View File

@@ -13,7 +13,7 @@ func (s *Server) handleRouteGet(c *gin.Context) {
appName := c.MustGet(api.AppName).(string) appName := c.MustGet(api.AppName).(string)
routePath := path.Clean("/" + c.MustGet(api.Path).(string)) routePath := path.Clean("/" + c.MustGet(api.Path).(string))
route, err := s.Datastore.GetRoute(ctx, appName, routePath) route, err := s.Datastore().GetRoute(ctx, appName, routePath)
if err != nil { if err != nil {
handleErrorResponse(c, err) handleErrorResponse(c, err)
return return

View File

@@ -19,13 +19,13 @@ func (s *Server) handleRouteList(c *gin.Context) {
// filter.PathPrefix = c.Query("path_prefix") TODO not hooked up // filter.PathPrefix = c.Query("path_prefix") TODO not hooked up
filter.Cursor, filter.PerPage = pageParams(c, true) filter.Cursor, filter.PerPage = pageParams(c, true)
routes, err := s.Datastore.GetRoutesByApp(ctx, appName, &filter) routes, err := s.Datastore().GetRoutesByApp(ctx, appName, &filter)
// if there are no routes for the app, check if the app exists to return // if there are no routes for the app, check if the app exists to return
// 404 if it does not // 404 if it does not
// TODO this should be done in front of this handler to even get here... // TODO this should be done in front of this handler to even get here...
if err == nil && len(routes) == 0 { if err == nil && len(routes) == 0 {
_, err = s.Datastore.GetApp(ctx, appName) _, err = s.Datastore().GetApp(ctx, appName)
} }
if err != nil { if err != nil {

View File

@@ -19,7 +19,7 @@ func testRouterAsync(ds models.Datastore, mq models.MessageQueue, rnr agent.Agen
s := &Server{ s := &Server{
Agent: rnr, Agent: rnr,
Router: gin.New(), Router: gin.New(),
Datastore: ds, datastore: ds,
MQ: mq, MQ: mq,
nodeType: ServerTypeFull, nodeType: ServerTypeFull,
} }

View File

@@ -66,7 +66,7 @@ func (s ServerNodeType) String() string {
type Server struct { type Server struct {
Router *gin.Engine Router *gin.Engine
Agent agent.Agent Agent agent.Agent
Datastore models.Datastore datastore models.Datastore
MQ models.MessageQueue MQ models.MessageQueue
LogDB models.LogStore LogDB models.LogStore
nodeType ServerNodeType nodeType ServerNodeType
@@ -164,7 +164,7 @@ func WithType(t ServerNodeType) ServerOption {
} }
func WithDatastore(ds models.Datastore) ServerOption { func WithDatastore(ds models.Datastore) ServerOption {
return func(s *Server) { s.Datastore = ds } return func(s *Server) { s.datastore = ds }
} }
func WithMQ(mq models.MessageQueue) ServerOption { func WithMQ(mq models.MessageQueue) ServerOption {
@@ -195,7 +195,7 @@ func New(ctx context.Context, opts ...ServerOption) *Server {
} }
if s.LogDB == nil { // TODO seems weird? if s.LogDB == nil { // TODO seems weird?
s.LogDB = s.Datastore s.LogDB = s.Datastore()
} }
// TODO we maybe should use the agent.DirectDataAccess in the /runner endpoints server side? // TODO we maybe should use the agent.DirectDataAccess in the /runner endpoints server side?
@@ -209,13 +209,13 @@ func New(ctx context.Context, opts ...ServerOption) *Server {
} }
default: default:
s.nodeType = ServerTypeFull s.nodeType = ServerTypeFull
if s.Datastore == nil || s.LogDB == nil || s.MQ == nil { if s.Datastore() == nil || s.LogDB == nil || s.MQ == nil {
logrus.Fatal("Full nodes must configure FN_DB_URL, FN_LOG_URL, FN_MQ_URL.") logrus.Fatal("Full nodes must configure FN_DB_URL, FN_LOG_URL, FN_MQ_URL.")
} }
// TODO force caller to use WithAgent option ? // TODO force caller to use WithAgent option ?
// TODO for tests we don't want cache, really, if we force WithAgent this can be fixed. cache needs to be moved anyway so that runner nodes can use it... // TODO for tests we don't want cache, really, if we force WithAgent this can be fixed. cache needs to be moved anyway so that runner nodes can use it...
s.Agent = agent.New(agent.NewCachedDataAccess(agent.NewDirectDataAccess(s.Datastore, s.LogDB, s.MQ))) s.Agent = agent.New(agent.NewCachedDataAccess(agent.NewDirectDataAccess(s.Datastore(), s.LogDB, s.MQ)))
} }
setMachineID() setMachineID()
@@ -376,13 +376,14 @@ func (s *Server) bindHandlers(ctx context.Context) {
if s.nodeType != ServerTypeRunner { if s.nodeType != ServerTypeRunner {
v1 := engine.Group("/v1") v1 := engine.Group("/v1")
v1.Use(setAppNameInCtx)
v1.Use(s.apiMiddlewareWrapper()) v1.Use(s.apiMiddlewareWrapper())
v1.GET("/apps", s.handleAppList) v1.GET("/apps", s.handleAppList)
v1.POST("/apps", s.handleAppCreate) v1.POST("/apps", s.handleAppCreate)
{ {
apps := v1.Group("/apps/:app") apps := v1.Group("/apps/:app")
apps.Use(appWrap) apps.Use(appNameCheck)
apps.GET("", s.handleAppGet) apps.GET("", s.handleAppGet)
apps.PATCH("", s.handleAppUpdate) apps.PATCH("", s.handleAppUpdate)
@@ -413,7 +414,7 @@ func (s *Server) bindHandlers(ctx context.Context) {
if s.nodeType != ServerTypeAPI { if s.nodeType != ServerTypeAPI {
runner := engine.Group("/r") runner := engine.Group("/r")
runner.Use(appWrap) runner.Use(appNameCheck)
runner.Any("/:app", s.handleFunctionCall) runner.Any("/:app", s.handleFunctionCall)
runner.Any("/:app/*route", s.handleFunctionCall) runner.Any("/:app/*route", s.handleFunctionCall)
} }
@@ -433,6 +434,10 @@ func (s *Server) bindHandlers(ctx context.Context) {
}) })
} }
func (s *Server) Datastore() models.Datastore {
return s.datastore
}
// returns the unescaped ?cursor and ?perPage values // returns the unescaped ?cursor and ?perPage values
// pageParams clamps 0 < ?perPage <= 100 and defaults to 30 if 0 // pageParams clamps 0 < ?perPage <= 100 and defaults to 30 if 0
// ignores parsing errors and falls back to defaults. // ignores parsing errors and falls back to defaults.

View File

@@ -60,4 +60,4 @@ fn deploy hello
## Example app ## Example app
See https://github.com/fnproject/fn/tree/master/examples/apps/hellos for a simple example. See https://github.com/fnproject/fn/tree/master/examples/apps/hellos for a simple example. Just clone it and run `fn deploy --all` to see it in action.

View File

@@ -2,4 +2,10 @@
This shows you how to organize functions into a full application and deploy them easily with one command. This shows you how to organize functions into a full application and deploy them easily with one command.
See [apps documentation](/docs/developers/apps.md) for details on how to use this. ```sh
fn deploy --all
```
Then surf to http://localhost:8080/r/helloapp
See [apps documentation](/docs/developers/apps.md) for more details on applications.

17
fnext/context.go Normal file
View File

@@ -0,0 +1,17 @@
package fnext
// good reading on this: https://twitter.com/sajma/status/757217773852487680
type contextKey string
// func (c contextKey) String() string {
// return "fnext context key " + string(c)
// }
// Keys for extensions to get things out of the context
var (
// MiddlewareControllerKey is a context key. It can be used in handlers with context.WithValue to
// access the MiddlewareContext.
MiddlewareControllerKey = contextKey("middleware_controller")
// AppNameKey
AppNameKey = contextKey("app_name")
)

View File

@@ -20,6 +20,25 @@ type AppListener interface {
BeforeAppDelete(ctx context.Context, app *models.App) error BeforeAppDelete(ctx context.Context, app *models.App) error
// AfterAppDelete called after deleting App in the database // AfterAppDelete called after deleting App in the database
AfterAppDelete(ctx context.Context, app *models.App) error AfterAppDelete(ctx context.Context, app *models.App) error
// BeforeAppGet called right before getting an app
BeforeAppGet(ctx context.Context, appName string) error
// AfterAppGet called after getting app from database
AfterAppGet(ctx context.Context, app *models.App) error
// BeforeAppsList called right before getting a list of all user's apps. Modify the filter to adjust what gets returned.
BeforeAppsList(ctx context.Context, filter *models.AppFilter) error
// AfterAppsList called after deleting getting a list of user's apps. apps is the result after applying AppFilter.
AfterAppsList(ctx context.Context, apps []*models.App) error
// TODO: WHAT IF THESE WERE CHANGE TO WRAPPERS INSTEAD OF LISTENERS, SORT OF LIKE MIDDLEWARE, EG
// AppCreate(ctx, app, next func(ctx, app) or next.AppCreate(ctx, app)) <- where func is the InsertApp function (ie: the corresponding Datastore function)
// Then instead of two two functions and modifying objects in the params, they get modified and then passed on. Eg:
// AppCreate(ctx, app, next) (app *models.App, err error) {
// // do stuff before
// app.Name = app.Name + "-12345"
// app, err = next.AppCreate(ctx, app)
// // do stuff after if you want
// return app, err
// }
} }
// CallListener enables callbacks around Call events // CallListener enables callbacks around Call events

View File

@@ -5,12 +5,6 @@ import (
"net/http" "net/http"
) )
var (
// MiddlewareControllerKey is a context key. It can be used in handlers with context.WithValue to
// access the MiddlewareContext.
MiddlewareControllerKey = contextKey("middleware-controller")
)
// MiddlewareController allows a bit more flow control to the middleware, since we multiple paths a request can go down. // MiddlewareController allows a bit more flow control to the middleware, since we multiple paths a request can go down.
// 1) Could be routed towards the API // 1) Could be routed towards the API
// 2) Could be routed towards a function // 2) Could be routed towards a function
@@ -43,10 +37,3 @@ type MiddlewareFunc func(next http.Handler) http.Handler
func (m MiddlewareFunc) Handle(next http.Handler) http.Handler { func (m MiddlewareFunc) Handle(next http.Handler) http.Handler {
return m(next) return m(next)
} }
// good reading on this: https://twitter.com/sajma/status/757217773852487680
type contextKey string
// func (c contextKey) String() string {
// return "fnext context key " + string(c)
// }

View File

@@ -37,4 +37,7 @@ type ExtServer interface {
AddRouteEndpoint(method, path string, handler ApiRouteHandler) AddRouteEndpoint(method, path string, handler ApiRouteHandler)
// AddRouteEndpoint adds an endpoints to /v1/apps/:app/routes/:route/x // AddRouteEndpoint adds an endpoints to /v1/apps/:app/routes/:route/x
AddRouteEndpointFunc(method, path string, handler func(w http.ResponseWriter, r *http.Request, app *models.App, route *models.Route)) AddRouteEndpointFunc(method, path string, handler func(w http.ResponseWriter, r *http.Request, app *models.App, route *models.Route))
// Datastore returns the Datastore Fn is using
Datastore() models.Datastore
} }