From 2489fd851f796e54dcd2de3d9f841f0d5eaee9ab Mon Sep 17 00:00:00 2001 From: Pedro Nasser Date: Tue, 26 Jul 2016 00:10:45 -0300 Subject: [PATCH 1/9] added wrapper on models; changed handlers; fixes --- api/models/app.go | 15 ++++++----- api/models/app_wrapper.go | 32 +++++++++++++++++++++++ api/models/apps_wrapper.go | 34 +++++++++++++++++++++++++ api/models/datastore.go | 4 +-- api/models/error.go | 2 +- api/models/route.go | 41 +++++++++++++++++------------- api/models/route_wrapper.go | 31 ++++++++++++++++++++++ api/models/routes_wrapper.go | 36 ++++++++++++++++++++++++++ api/server/router/apps_create.go | 14 +++++++--- api/server/router/apps_get.go | 2 +- api/server/router/apps_list.go | 2 +- api/server/router/apps_update.go | 11 +------- api/server/router/router.go | 2 +- api/server/router/routes_create.go | 24 ++++++++++------- api/server/router/routes_get.go | 2 +- api/server/router/routes_list.go | 2 +- api/server/router/routes_update.go | 8 +++--- 17 files changed, 203 insertions(+), 59 deletions(-) create mode 100644 api/models/app_wrapper.go create mode 100644 api/models/apps_wrapper.go create mode 100644 api/models/route_wrapper.go create mode 100644 api/models/routes_wrapper.go diff --git a/api/models/app.go b/api/models/app.go index b203cc94a..a349a1089 100644 --- a/api/models/app.go +++ b/api/models/app.go @@ -5,13 +5,14 @@ import "errors" type Apps []*App var ( - ErrAppsCreate = errors.New("Could not create app") - ErrAppsUpdate = errors.New("Could not update app") - ErrAppsRemoving = errors.New("Could not remove app from datastore") - ErrAppsGet = errors.New("Could not get app from datastore") - ErrAppsList = errors.New("Could not list apps from datastore") - ErrAppsNotFound = errors.New("App not found") - ErrAppNothingToUpdate = errors.New("Nothing to update") + ErrAppsCreate = errors.New("Could not create app") + ErrAppsUpdate = errors.New("Could not update app") + ErrAppsRemoving = errors.New("Could not remove app from datastore") + ErrAppsGet = errors.New("Could not get app from datastore") + ErrAppsList = errors.New("Could not list apps from datastore") + ErrAppsNotFound = errors.New("App not found") + ErrAppsNothingToUpdate = errors.New("Nothing to update") + ErrAppsMissingNew = errors.New("Missing new application") ) type App struct { diff --git a/api/models/app_wrapper.go b/api/models/app_wrapper.go new file mode 100644 index 000000000..815888261 --- /dev/null +++ b/api/models/app_wrapper.go @@ -0,0 +1,32 @@ +package models + +import "github.com/go-openapi/errors" + +type AppWrapper struct { + App *App `json:"app"` +} + +func (m *AppWrapper) Validate() error { + var res []error + + if err := m.validateApp(); err != nil { + // prop + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *AppWrapper) validateApp() error { + + if m.App != nil { + if err := m.App.Validate(); err != nil { + return err + } + } + + return nil +} diff --git a/api/models/apps_wrapper.go b/api/models/apps_wrapper.go new file mode 100644 index 000000000..98d683d22 --- /dev/null +++ b/api/models/apps_wrapper.go @@ -0,0 +1,34 @@ +package models + +import ( + strfmt "github.com/go-openapi/strfmt" + "github.com/go-openapi/validate" + + "github.com/go-openapi/errors" +) + +type AppsWrapper struct { + Apps []*App `json:"apps"` +} + +func (m *AppsWrapper) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateApps(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *AppsWrapper) validateApps(formats strfmt.Registry) error { + + if err := validate.Required("apps", "body", m.Apps); err != nil { + return err + } + + return nil +} diff --git a/api/models/datastore.go b/api/models/datastore.go index 3481ca86f..4ffa3fb8a 100644 --- a/api/models/datastore.go +++ b/api/models/datastore.go @@ -17,6 +17,6 @@ func ApplyAppFilter(app *App, filter *AppFilter) bool { } func ApplyRouteFilter(route *Route, filter *RouteFilter) bool { - return (filter.Path != "" && route.Path == filter.Path) && - (filter.AppName != "" && route.AppName == filter.AppName) + return (filter.Path == "" || route.Path == filter.Path) && + (filter.AppName == "" || route.AppName == filter.AppName) } diff --git a/api/models/error.go b/api/models/error.go index 6996f8083..ae7dc12ad 100644 --- a/api/models/error.go +++ b/api/models/error.go @@ -11,5 +11,5 @@ func (m *Error) Validate() error { } var ( - ErrInvalidJSON = errors.New("Could not create app") + ErrInvalidJSON = errors.New("Invalid JSON") ) diff --git a/api/models/route.go b/api/models/route.go index 501426671..a100d1127 100644 --- a/api/models/route.go +++ b/api/models/route.go @@ -3,27 +3,28 @@ package models import ( "errors" "net/http" + + apiErrors "github.com/go-openapi/errors" ) var ( - ErrRoutesCreate = errors.New("Could not create route") - ErrRoutesUpdate = errors.New("Could not update route") - ErrRoutesRemoving = errors.New("Could not remove route from datastore") - ErrRoutesGet = errors.New("Could not get route from datastore") - ErrRoutesList = errors.New("Could not list routes from datastore") - ErrRoutesNotFound = errors.New("Route not found") + ErrRoutesCreate = errors.New("Could not create route") + ErrRoutesUpdate = errors.New("Could not update route") + ErrRoutesRemoving = errors.New("Could not remove route from datastore") + ErrRoutesGet = errors.New("Could not get route from datastore") + ErrRoutesList = errors.New("Could not list routes from datastore") + ErrRoutesNotFound = errors.New("Route not found") + ErrRoutesMissingNew = errors.New("Missing new route") ) type Routes []*Route type Route struct { - Name string `json:"name"` - AppName string `json:"appname"` - Path string `json:"path"` - Image string `json:"image"` - Type string `json:"type,omitempty"` - ContainerPath string `json:"container_path,omitempty"` - Headers http.Header `json:"headers,omitempty"` + Name string `json:"name"` + AppName string `json:"appname"` + Path string `json:"path"` + Image string `json:"image"` + Headers http.Header `json:"headers,omitempty"` } var ( @@ -34,20 +35,26 @@ var ( ) func (r *Route) Validate() error { + var res []error + if r.Name == "" { - return ErrRoutesValidationName + res = append(res, ErrRoutesValidationAppName) } if r.Image == "" { - return ErrRoutesValidationImage + res = append(res, ErrRoutesValidationImage) } if r.AppName == "" { - return ErrRoutesValidationAppName + res = append(res, ErrRoutesValidationAppName) } if r.Path == "" { - return ErrRoutesValidationPath + res = append(res, ErrRoutesValidationPath) + } + + if len(res) > 0 { + return apiErrors.CompositeValidationError(res...) } return nil diff --git a/api/models/route_wrapper.go b/api/models/route_wrapper.go new file mode 100644 index 000000000..0d7e21bdc --- /dev/null +++ b/api/models/route_wrapper.go @@ -0,0 +1,31 @@ +package models + +import "github.com/go-openapi/errors" + +type RouteWrapper struct { + Route *Route `json:"route"` +} + +func (m *RouteWrapper) Validate() error { + var res []error + + if err := m.validateRoute(); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *RouteWrapper) validateRoute() error { + + if m.Route != nil { + if err := m.Route.Validate(); err != nil { + return err + } + } + + return nil +} diff --git a/api/models/routes_wrapper.go b/api/models/routes_wrapper.go new file mode 100644 index 000000000..b61e0aab6 --- /dev/null +++ b/api/models/routes_wrapper.go @@ -0,0 +1,36 @@ +package models + +import ( + strfmt "github.com/go-openapi/strfmt" + "github.com/go-openapi/validate" + + "github.com/go-openapi/errors" +) + +type RoutesWrapper struct { + Cursor string `json:"cursor,omitempty"` + Error *ErrorBody `json:"error,omitempty"` + Routes []*Route `json:"routes"` +} + +func (m *RoutesWrapper) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateRoutes(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *RoutesWrapper) validateRoutes(formats strfmt.Registry) error { + + if err := validate.Required("routes", "body", m.Routes); err != nil { + return err + } + + return nil +} diff --git a/api/server/router/apps_create.go b/api/server/router/apps_create.go index 86b7d702b..4bfa09760 100644 --- a/api/server/router/apps_create.go +++ b/api/server/router/apps_create.go @@ -12,22 +12,28 @@ func handleAppCreate(c *gin.Context) { store := c.MustGet("store").(models.Datastore) log := c.MustGet("log").(logrus.FieldLogger) - app := &models.App{} + wapp := &models.AppWrapper{} - err := c.BindJSON(app) + err := c.BindJSON(wapp) if err != nil { log.WithError(err).Debug(models.ErrInvalidJSON) c.JSON(http.StatusBadRequest, simpleError(models.ErrInvalidJSON)) return } - if err := app.Validate(); err != nil { + if wapp.App == nil { + log.Debug(models.ErrAppsMissingNew) + c.JSON(http.StatusBadRequest, simpleError(models.ErrAppsMissingNew)) + return + } + + if err := wapp.Validate(); err != nil { log.Error(err) c.JSON(http.StatusInternalServerError, simpleError(err)) return } - app, err = store.StoreApp(app) + app, err := store.StoreApp(wapp.App) if err != nil { log.WithError(err).Debug(models.ErrAppsCreate) c.JSON(http.StatusInternalServerError, simpleError(models.ErrAppsCreate)) diff --git a/api/server/router/apps_get.go b/api/server/router/apps_get.go index 47f5be75f..07ace61c6 100644 --- a/api/server/router/apps_get.go +++ b/api/server/router/apps_get.go @@ -40,5 +40,5 @@ func handleAppGet(c *gin.Context) { app.Routes = routes - c.JSON(http.StatusOK, app) + c.JSON(http.StatusOK, &models.AppWrapper{app}) } diff --git a/api/server/router/apps_list.go b/api/server/router/apps_list.go index d716316c3..7746dc1e2 100644 --- a/api/server/router/apps_list.go +++ b/api/server/router/apps_list.go @@ -21,5 +21,5 @@ func handleAppList(c *gin.Context) { return } - c.JSON(http.StatusOK, apps) + c.JSON(http.StatusOK, &models.AppsWrapper{apps}) } diff --git a/api/server/router/apps_update.go b/api/server/router/apps_update.go index dc7c3db7c..f00f331ce 100644 --- a/api/server/router/apps_update.go +++ b/api/server/router/apps_update.go @@ -21,14 +21,5 @@ func handleAppUpdate(c *gin.Context) { return } - // app.Name = c.Param("app") - - // app, err = store.StoreApp(app) - // if err != nil { - // log.WithError(err).Debug(models.ErrAppsUpdate) - // c.JSON(http.StatusInternalServerError, simpleError(models.ErrAppsUpdate)) - // return - // } - - c.JSON(http.StatusOK, simpleError(models.ErrAppNothingToUpdate)) + c.JSON(http.StatusOK, simpleError(models.ErrAppsNothingToUpdate)) } diff --git a/api/server/router/router.go b/api/server/router/router.go index acf7129d1..8178be917 100644 --- a/api/server/router/router.go +++ b/api/server/router/router.go @@ -15,7 +15,7 @@ func Start(engine *gin.Engine) { v1.POST("/apps", handleAppCreate) v1.GET("/apps/:app", handleAppGet) - v1.POST("/apps/:app", handleAppUpdate) + v1.PUT("/apps/:app", handleAppUpdate) v1.DELETE("/apps/:app", handleAppDestroy) apps := v1.Group("/apps/:app") diff --git a/api/server/router/routes_create.go b/api/server/router/routes_create.go index 11d73393b..c85c6620a 100644 --- a/api/server/router/routes_create.go +++ b/api/server/router/routes_create.go @@ -12,31 +12,37 @@ func handleRouteCreate(c *gin.Context) { store := c.MustGet("store").(models.Datastore) log := c.MustGet("log").(logrus.FieldLogger) - route := &models.Route{} + wroute := &models.RouteWrapper{} - err := c.BindJSON(route) + err := c.BindJSON(wroute) if err != nil { - log.WithError(err).Debug(models.ErrInvalidJSON) + log.WithError(err).Error(models.ErrInvalidJSON) c.JSON(http.StatusBadRequest, simpleError(models.ErrInvalidJSON)) return } - route.AppName = c.Param("app") + if wroute.Route == nil { + log.WithError(err).Error(models.ErrInvalidJSON) + c.JSON(http.StatusBadRequest, simpleError(models.ErrRoutesMissingNew)) + return + } - if err := route.Validate(); err != nil { + wroute.Route.AppName = c.Param("app") + + if err := wroute.Validate(); err != nil { log.Error(err) c.JSON(http.StatusInternalServerError, simpleError(err)) return } - app, err := store.GetApp(route.AppName) + app, err := store.GetApp(wroute.Route.AppName) if err != nil { log.WithError(err).Error(models.ErrAppsGet) c.JSON(http.StatusInternalServerError, simpleError(models.ErrAppsGet)) return } if app == nil { - app, err = store.StoreApp(&models.App{Name: route.AppName}) + app, err = store.StoreApp(&models.App{Name: wroute.Route.AppName}) if err != nil { log.WithError(err).Error(models.ErrAppsCreate) c.JSON(http.StatusInternalServerError, simpleError(models.ErrAppsCreate)) @@ -44,9 +50,9 @@ func handleRouteCreate(c *gin.Context) { } } - route, err = store.StoreRoute(route) + route, err := store.StoreRoute(wroute.Route) if err != nil { - log.WithError(err).Debug(models.ErrRoutesCreate) + log.WithError(err).Error(models.ErrRoutesCreate) c.JSON(http.StatusInternalServerError, simpleError(models.ErrRoutesCreate)) return } diff --git a/api/server/router/routes_get.go b/api/server/router/routes_get.go index cc6983b0b..d6f1dfe7d 100644 --- a/api/server/router/routes_get.go +++ b/api/server/router/routes_get.go @@ -24,5 +24,5 @@ func handleRouteGet(c *gin.Context) { log.WithFields(logrus.Fields{"route": route}).Debug("Got route") - c.JSON(http.StatusOK, route) + c.JSON(http.StatusOK, &models.RouteWrapper{route}) } diff --git a/api/server/router/routes_list.go b/api/server/router/routes_list.go index fdf05a62f..6735ba0d9 100644 --- a/api/server/router/routes_list.go +++ b/api/server/router/routes_list.go @@ -27,5 +27,5 @@ func handleRouteList(c *gin.Context) { log.WithFields(logrus.Fields{"routes": routes}).Debug("Got routes") - c.JSON(http.StatusOK, routes) + c.JSON(http.StatusOK, &models.RoutesWrapper{Routes: routes}) } diff --git a/api/server/router/routes_update.go b/api/server/router/routes_update.go index c00554a75..188c33252 100644 --- a/api/server/router/routes_update.go +++ b/api/server/router/routes_update.go @@ -12,19 +12,19 @@ func handleRouteUpdate(c *gin.Context) { store := c.MustGet("store").(models.Datastore) log := c.MustGet("log").(logrus.FieldLogger) - route := &models.Route{} + wroute := &models.RouteWrapper{} appName := c.Param("app") - err := c.BindJSON(route) + err := c.BindJSON(wroute) if err != nil { log.WithError(err).Debug(models.ErrInvalidJSON) c.JSON(http.StatusBadRequest, simpleError(models.ErrInvalidJSON)) return } - route.AppName = appName + wroute.Route.AppName = appName - route, err = store.StoreRoute(route) + route, err := store.StoreRoute(wroute.Route) if err != nil { log.WithError(err).Debug(models.ErrAppsCreate) c.JSON(http.StatusInternalServerError, simpleError(models.ErrAppsCreate)) From e7e067ff71ace7e65e4d5b76cf392b214b422fa9 Mon Sep 17 00:00:00 2001 From: Pedro Nasser Date: Tue, 26 Jul 2016 00:10:53 -0300 Subject: [PATCH 2/9] added swagger --- api/swagger.yml | 324 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 api/swagger.yml diff --git a/api/swagger.yml b/api/swagger.yml new file mode 100644 index 000000000..c56cde5a9 --- /dev/null +++ b/api/swagger.yml @@ -0,0 +1,324 @@ +# This is the IronFunctions API spec +# If you make changes here, remember to run `go generate` in routeserver/ and +# tasker/ to make sure you use the changes. + +swagger: '2.0' +info: + title: IronFunctions + description: + version: "0.0.1" +# the domain of the service +host: "localhost:8080" +# array of all schemes that your API supports +schemes: + - https + - http +# will be prefixed to all paths +basePath: /v1 +consumes: + - application/json +produces: + - application/json +paths: + /apps: + get: + summary: "Get all app names." + description: "Get a list of all the apps in the system." + tags: + - Apps + responses: + 200: + description: List of apps. + schema: + $ref: '#/definitions/AppsWrapper' + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + post: + summary: "Post new app" + description: "Insert a new app" + tags: + - Apps + parameters: + - name: body + in: body + description: App to post. + required: true + schema: + $ref: '#/definitions/AppWrapper' + responses: + 200: + description: App details and stats. + schema: + $ref: '#/definitions/AppWrapper' + 400: + description: Parameters are missing or invalid. + schema: + $ref: '#/definitions/Error' + 500: + description: Could not accept app due to internal error. + schema: + $ref: '#/definitions/Error' + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + + + /apps/{app}: + get: + summary: "Get information for a app." + description: "This gives more details about a app, such as statistics." + tags: + - Apps + parameters: + - name: name + in: path + description: name of the app. + required: true + type: string + responses: + 200: + description: App details and stats. + schema: + $ref: '#/definitions/AppWrapper' + 404: + description: App does not exist. + schema: + $ref: '#/definitions/Error' + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + put: + summary: "Create/update a app." + description: "You can set app level settings here. " + tags: + - Apps + parameters: + - name: app + in: path + description: name of the app. + required: true + type: string + - name: body + in: body + description: App to post. + required: true + schema: + $ref: '#/definitions/AppWrapper' + responses: + 200: + description: App details and stats. + schema: + $ref: '#/definitions/AppWrapper' + 400: + description: Parameters are missing or invalid. + schema: + $ref: '#/definitions/Error' + 500: + description: Could not accept app due to internal error. + schema: + $ref: '#/definitions/Error' + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + + /apps/{app}/routes: + post: + summary: Enqueue Route + description: | + Enqueues route(s). If any of the routes is invalid, none of the routes are enqueued. + tags: + - Routes + parameters: + - name: app + in: path + description: name of the app. + required: true + type: string + - name: body + in: body + description: Array of routes to post. + required: true + schema: + $ref: '#/definitions/NewRoutesWrapper' + responses: + 201: + description: Route created + schema: + $ref: '#/definitions/RoutesWrapper' + 400: + description: One or more of the routes were invalid due to parameters being missing or invalid. + schema: + $ref: '#/definitions/Error' + 500: + description: Could not accept routes due to internal error. + schema: + $ref: '#/definitions/Error' + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + + get: + summary: Get route list by app name. + description: This will list routes for a particular app. + tags: + - Routes + parameters: + - name: app + in: path + description: Name of app for this set of routes. + required: true + type: string + responses: + 200: + description: Route information + schema: + $ref: '#/definitions/RoutesWrapper' + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + + /apps/{app}/routes/{route}: + get: + summary: Gets route by name + description: Gets a route by name. + tags: + - Routes + parameters: + - name: app + in: path + description: Name of app for this set of routes. + required: true + type: string + - name: route + in: path + description: Route name + required: true + type: string + responses: + 200: + description: Route information + schema: + $ref: '#/definitions/RouteWrapper' + 404: + description: Route does not exist. + schema: + $ref: '#/definitions/Error' + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + + delete: + summary: Deletes the route + tags: + - Routes + description: Deletes the route. + parameters: + - name: app + in: path + description: Name of app for this set of routes. + required: true + type: string + - name: route + in: path + description: Route name + required: true + type: string + responses: + 200: + description: Route successfully deleted. Deletion succeeds even on routes that do not exist. + +definitions: + Route: + allOf: + - type: object + properties: + name: + type: string + description: "Route name" + readOnly: true + app_name: + type: string + description: "App this route belongs to." + readOnly: true + path: + type: string + description: URL path that will be matched to this route + image: + description: Name of Docker image to use in this route. You should include the image tag, which should be a version number, to be more accurate. Can be overridden on a per route basis with route.image. + type: string + headers: + type: string + description: Map of http headers that will be sent with the response + + App: + type: object + properties: + name: + type: string + description: "Name of this app. Must be different than the image name. Can ony contain alphanumeric, -, and _." + readOnly: true + + RoutesWrapper: + type: object + required: + - routes + properties: + routes: + type: array + items: + $ref: '#/definitions/Route' + cursor: + type: string + description: Used to paginate results. If this is returned, pass it into the same query again to get more results. + error: + $ref: '#/definitions/ErrorBody' + + RouteWrapper: + type: object + required: + - route + properties: + route: + $ref: '#/definitions/Route' + + AppsWrapper: + type: object + required: + - apps + properties: + apps: + type: array + items: + $ref: '#/definitions/App' + + AppWrapper: + type: object + required: + - app + properties: + app: + $ref: '#/definitions/App' + + ErrorBody: + type: object + properties: + message: + type: string + readOnly: true + fields: + type: string + readOnly: true + + Error: + type: object + properties: + error: + $ref: '#/definitions/ErrorBody' From c8303a7295dafab79cf93f0f886c4196acadb6a8 Mon Sep 17 00:00:00 2001 From: Pedro Nasser Date: Wed, 27 Jul 2016 17:50:27 -0300 Subject: [PATCH 3/9] remove unexistent route properties --- api/server/datastore/postgres/postgres.go | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/api/server/datastore/postgres/postgres.go b/api/server/datastore/postgres/postgres.go index 8adf6d52e..c659c6194 100644 --- a/api/server/datastore/postgres/postgres.go +++ b/api/server/datastore/postgres/postgres.go @@ -17,8 +17,6 @@ CREATE TABLE IF NOT EXISTS routes ( path text NOT NULL, app_name character varying(256) NOT NULL, image character varying(256) NOT NULL, - type character varying(256) NOT NULL, - container_path text NOT NULL, headers text NOT NULL );` @@ -26,7 +24,7 @@ const appsTableCreate = `CREATE TABLE IF NOT EXISTS apps ( name character varying(256) NOT NULL PRIMARY KEY );` -const routeSelector = `SELECT name, path, app_name, image, type, container_path, headers FROM routes` +const routeSelector = `SELECT name, path, app_name, image, headers FROM routes` type rowScanner interface { Scan(dest ...interface{}) error @@ -162,22 +160,18 @@ func (ds *PostgresDatastore) StoreRoute(route *models.Route) (*models.Route, err _, err = ds.db.Exec(` INSERT INTO routes ( name, app_name, path, image, - type, container_path, headers + headers ) VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (name) DO UPDATE SET path = $3, image = $4, - type = $5, - container_path = $6, - headers = $7; + headers = $5; `, route.Name, route.AppName, route.Path, route.Image, - route.Type, - route.ContainerPath, headers, ) @@ -206,8 +200,6 @@ func scanRoute(scanner rowScanner, route *models.Route) error { &route.Path, &route.AppName, &route.Image, - &route.Type, - &route.ContainerPath, &headerStr, ) From f09a4cc94fef77f5617019783df7e4664eff61ec Mon Sep 17 00:00:00 2001 From: Pedro Nasser Date: Wed, 27 Jul 2016 17:51:23 -0300 Subject: [PATCH 4/9] use *Route to bind json route creation --- api/server/router/routes_create.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/api/server/router/routes_create.go b/api/server/router/routes_create.go index c85c6620a..5733a081e 100644 --- a/api/server/router/routes_create.go +++ b/api/server/router/routes_create.go @@ -12,37 +12,37 @@ func handleRouteCreate(c *gin.Context) { store := c.MustGet("store").(models.Datastore) log := c.MustGet("log").(logrus.FieldLogger) - wroute := &models.RouteWrapper{} + route := &models.Route{} - err := c.BindJSON(wroute) + err := c.BindJSON(route) if err != nil { log.WithError(err).Error(models.ErrInvalidJSON) c.JSON(http.StatusBadRequest, simpleError(models.ErrInvalidJSON)) return } - if wroute.Route == nil { + if route == nil { log.WithError(err).Error(models.ErrInvalidJSON) c.JSON(http.StatusBadRequest, simpleError(models.ErrRoutesMissingNew)) return } - wroute.Route.AppName = c.Param("app") + route.AppName = c.Param("app") - if err := wroute.Validate(); err != nil { + if err := route.Validate(); err != nil { log.Error(err) c.JSON(http.StatusInternalServerError, simpleError(err)) return } - app, err := store.GetApp(wroute.Route.AppName) + app, err := store.GetApp(route.AppName) if err != nil { log.WithError(err).Error(models.ErrAppsGet) c.JSON(http.StatusInternalServerError, simpleError(models.ErrAppsGet)) return } if app == nil { - app, err = store.StoreApp(&models.App{Name: wroute.Route.AppName}) + app, err = store.StoreApp(&models.App{Name: route.AppName}) if err != nil { log.WithError(err).Error(models.ErrAppsCreate) c.JSON(http.StatusInternalServerError, simpleError(models.ErrAppsCreate)) @@ -50,7 +50,7 @@ func handleRouteCreate(c *gin.Context) { } } - route, err := store.StoreRoute(wroute.Route) + route, err = store.StoreRoute(route) if err != nil { log.WithError(err).Error(models.ErrRoutesCreate) c.JSON(http.StatusInternalServerError, simpleError(models.ErrRoutesCreate)) From 338498e94d567b8170c70a148e1921e21e8e754a Mon Sep 17 00:00:00 2001 From: Pedro Nasser Date: Wed, 27 Jul 2016 17:51:51 -0300 Subject: [PATCH 5/9] update docs and contributing --- .gitignore | 1 + CONTRIBUTING.md | 23 ++-- README.md | 106 +++++++++++-------- docs/api.md | 0 docs/database/boltdb.md | 11 ++ docs/database/postgres.md | 34 ++++++ example.env | 9 -- gateway.pem | 23 ---- glide.lock | 118 ++++++++++----------- glide.yaml | 19 ++-- hack/api.sh | 5 + build.sh => hack/build.sh | 0 release.sh => hack/release.sh | 3 +- proxy/reverser.go | 192 ---------------------------------- 14 files changed, 194 insertions(+), 350 deletions(-) create mode 100644 docs/api.md create mode 100644 docs/database/boltdb.md create mode 100644 docs/database/postgres.md delete mode 100644 example.env delete mode 100644 gateway.pem create mode 100755 hack/api.sh rename build.sh => hack/build.sh (100%) rename release.sh => hack/release.sh (78%) delete mode 100644 proxy/reverser.go diff --git a/.gitignore b/.gitignore index 5e1899827..98b50c36e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ vendor/ /gateway /functions bolt.db +.glide/ private.sh .env diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d8851556d..7d6df9b99 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,23 +1,24 @@ +## Building -## Building/Testing - -Build: - -```sh -# one time: +First time or when a dependency changes or when the API changes, run: +``` glide install -# then every time -./build.sh ``` -Test it, the iron token and project id are for cache. +To quick build and run (using default database): ```sh -docker run --env-file .env --rm -it --privileged -p 8080:8080 iron/functions +hack/api.sh +``` + +To build the docker image: + +```sh +hack/build.sh ``` ## Releasing ```sh -./release.sh +hack/release.sh ``` diff --git a/README.md b/README.md index 3f26e586d..d4d7331b3 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,80 @@ -Note: currently running at: http://gateway.iron.computer:8080/ - # IronFunctions -First, let's fire up an IronFunctions instance. Copy the [example.env](example.env) file into a file named `.env` and fill in the missing values. +## [Overview](/iron-io/functions/blob/master/OVERVIEW.md) -Then start your functions instance: +## Quick Start + +First let's start our IronFunctions API ``` -docker run --env-file .env --rm -it --privileged -p 8080:8080 iron/functions +docker run --rm -it -p 8080:8080 iron/functions ``` -## Usage +This command will quickly start our API using the default database `Bolt` running on `:8080` -First things first, create an app/service: -TOOD: App or service?? +Now that we have our API up and running we can quickly create our first function -### Create App +``` +curl -H "Content-Type: application/json" -X POST -d '{ + "name": "MyRoute" + "path": "/myroute" + "image": "iron/hello" +}' http://localhost:8080/v1/apps/myapp/routes +``` + +Done. Now you have our first IronFunctions route ready. + +Now let's test our new route. + +``` +curl http://localhost:8080/r/myapp/myroute +``` + +## Configuring your API + +### Databases + +These are the current databases supported by IronFunctions: + +- [Running with BoltDB](/iron-io/functions/blob/master/docs/database/boltdb.md) +- [Running with Postgres](/iron-io/functions/blob/master/docs/database/postgres.md) + +## API Usage + +### Creating applications ```sh -iron create app APP_NAME -# OR -curl -H "Content-Type: application/json" -X POST -d '{"name":"APP_NAME"}' http://localhost:8080/api/v1/apps +curl -H "Content-Type: application/json" -X POST -d '{ + "name":"APP_NAME" +}' http://localhost:8080/v1/apps ``` -### Create a Route for your Function +### Creating routes in a application Now add routes to the app. First we'll add a route to the output of a docker container: ```sh -iron add route myapp /hello iron/hello -# OR -curl -H "Content-Type: application/json" -X POST -d '{"path":"/hello", "image":"iron/hello"}' http://localhost:8080/api/v1/apps/myapp/routes -``` - -And how about a [slackbot](https://github.com/treeder/slackbots/tree/master/guppy) too: - -```sh -curl -H "Content-Type: application/json" -X POST -d '{"path":"/guppy","image":"treeder/guppy:0.0.2", "content_type": "application/json"}' http://localhost:8080/api/v1/apps/myapp/routes +curl -H "Content-Type: application/json" -X POST -d '{ + "name": "hello", + "path":"/hello", + "image":"iron/hello" +}' http://localhost:8080/v1/apps/myapp/routes ``` ### Calling your Function -Surf to your function: http://localhost:8080/hello?app=APP_NAME . Boom! +``` +curl http://localhost:8080/r/myapp/hello +``` -#### To pass in data to your function, +### To pass in data to your function, Your function will get the body of the request as is, and the headers of the request will be passed in as env vars. ```sh -curl -H "Content-Type: application/json" -X POST -d '{"name":"Johnny"}' http://localhost:8080/hello?app=APP_NAME +curl -H "Content-Type: application/json" -X POST -d '{ + "name":"Johnny" +}' http://localhost:8080/r/myapp/hello ``` ### Using IronFunctions Hosted by Iron.io @@ -56,38 +82,26 @@ curl -H "Content-Type: application/json" -X POST -d '{"name":"Johnny"}' http://l Simply point to https://functions.iron.io instead of localhost and add your Iron.io Authentication header (TODO: link), like this: ```sh -curl -H "Authorization: Bearer IRON_TOKEN" -H "Content-Type: application/json" -X POST -d '{"name":"APP_NAME"}' https://functions.iron.io/api/v1/apps +curl -H "Authorization: Bearer IRON_TOKEN" -H "Content-Type: application/json" -X POST -d '{"name":"APP_NAME"}' https://functions.iron.io/v1/apps ``` And you'll get an ironfunctions.com host: ``` -APP_NAME.ironfunctions.com/PATH +APP_NAME.USER_ID.ironfunctions.com/PATH ``` -### Updating Your Images +## [Examples](/iron-io/functions/blob/master/examples) -Tag your images with a version, eg `treeder/guppy:0.0.5` then use that including the tag and update -the route. - -## Examples - -TODO: Link to examples in various languages -TODO: Link to slackbots (easiest way to host slackbots?) - -## Operations - -This is info on how to run and manage IronFunctions. - -### Logging - -Run logspout container on your server. - -#### Monitoring +## Logging TODO -### Scaling +## Monitoring + +TODO + +## Scaling TODO diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/database/boltdb.md b/docs/database/boltdb.md new file mode 100644 index 000000000..23c3391ed --- /dev/null +++ b/docs/database/boltdb.md @@ -0,0 +1,11 @@ +# IronFunctions using BoltDB + +BoltDB is the default database, you just need to run the API. + +## Persistent + +To keep it persistent you add a volume flag to the command: + +``` +docker run --rm -it -v $PWD/bold.db:/app/bolt.db -p 8080:8080 iron/functions +``` \ No newline at end of file diff --git a/docs/database/postgres.md b/docs/database/postgres.md new file mode 100644 index 000000000..7c232f5d5 --- /dev/null +++ b/docs/database/postgres.md @@ -0,0 +1,34 @@ +# IronFunctions using Postgres + +Let's presuppose you don't have even a postgres DB ready. + +### 1. Let's start a postgres instance: + +``` +docker run --name iron-postgres \ + -e POSTGRES_PASSWORD=ironfunctions -d postgres +``` + +### 2. Now let's create a new database to IronFunctions + +Creating database: + +``` +docker run -it --rm --link iron-postgres:postgres postgres \ + psql -h postgres -U postgres -c "CREATE DATABASE funcs;" +``` + +Granting access to postgres user + +``` +docker run -it --rm --link iron-postgres:postgres postgres \ + psql -h postgres -U postgres -c 'GRANT ALL PRIVILEGES ON DATABASE funcs TO postgres;' +``` + +### 3. Now let's start IronFunctions connecting to our new postgres instance + +``` +docker run --rm --link "iron-postgres:postgres" \ + -e "DB=postgres://postgres:ironfunctions@postgres/funcs?sslmode=disable" \ + -it -p 8080:8080 iron/functions +``` \ No newline at end of file diff --git a/example.env b/example.env deleted file mode 100644 index 4cc3f0c2f..000000000 --- a/example.env +++ /dev/null @@ -1,9 +0,0 @@ -# For IronCache -IRON_TOKEN=X -IRON_PROJECT_ID=X - -# For CloudFlare dns support -CLOUDFLARE_EMAIL=you@example.com -CLOUDFLARE_API_KEY=X -# See comments here to get zone id https://blog.cloudflare.com/cloudflare-tips-frequently-used-cloudflare-ap/#comment-2412200222 -CLOUDFLARE_ZONE_ID=y diff --git a/gateway.pem b/gateway.pem deleted file mode 100644 index 7124fbd84..000000000 --- a/gateway.pem +++ /dev/null @@ -1,23 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEAhof/Rgb5YUGUCynxYAkbkSRQqIaaZem7g6r/lyePBk6IKzjia6QA+Ut+1KZt -uGrNXFR0THtfEWCFjIPBJfYLGmkG1gaYocWJUu/b3be3rKGlLWEuSpfHsYyLh4803QNU79Uu0ft8 -ODB4QJl54WImD1JKzAZyarDalyb+GKbnU5NAULBbTccbGFbNSwPwebvoK9G6Z8qWChPqsYAZxfyC -D2LBt0PANwB+haC6Rj0t99R6mtLRz/iKYaHz26d6UxSzNsXArJlhSCABHdQ71rbPkO0M9PvJrhfg -y+bLA4sMrHvOSjEDWGY+j1qqEYXSc/Rwe5SMd8kV7i902ks7PjcCZQIDAQABAoIBAHjvlHk9F71o -GE+Y2tV8Gn31aVS1++IVpW2NsMoO07HVsu836cLd4co5JcDAA+4+hHG1sf53AVU7sZJJdr5LWlvZ -gj2wHFGApBwcZ0f/OWxEu5n5vIVtwCRJtbyc7eaochhPShGVw2s3l0JrNXd4pcIsNfUG7qAeb8Jl -WRKMJ3OmoEMOz5M3scRypQKOulRjO6RMJCtbl4AntMYNF7cdWeuIJ3eaMD8HaYbkr1USrwGk65QC -mKdUcNl9k++Txuf7UtbRB0apFsMnAKRPUTU+9TPGwMsZSzszk8TClMNO1ALYKKY5mE+cPqrAl5gC -ZpPOf45oT+2lxktqq5u5N8XMFoECgYEA16eGE3inh23EVly5ARnowGgFtorZsI6XIJHkYsETRocf -GAvQrEMAuFWZy0n5TlNBfzPhHh9rUXWCKlbldgnDgWKpphPux3UXTqTUak+j6397rtEP85RfYqDm -QxtW0uOkKkZSGyXXEYBwTbCsQBH28VZsJEYVe+G3uXPcUu3WEZUCgYEAn7Mwgk/JP5wgYo4E3gga -fOi0/de11MV5ad337qfdUC1pf1ju9q2CyHaV3g6eo2OnynGZHYq5qlyLWoi/hTr6A+yMZSnQ15io -9ker4uyAX6DdVDmWK9uErwrqLAV+Q6HoVmxoyBbMihQW8TqX/5jZegxqipDW16+qOFxtPbZhmZEC -gYEAlD85UBFVOSggHC5Jj5Q8CGh55O62j0S2Z1Fjau/HTGh+24zjukelKxLNUo5br5hUIhmL26VF -pQ3emTR7MRWtLDii3uQ89ShtCUcOLrbovG86mwZkrNGGcMqi/+a/XOHYbKdCsh7lJcbhbMbS4oh2 -9ZivZpA3HJ4iKn6XKvsMebECgYEAhNWXU8zpqG9EwLVAdy5mWd92LG5wYDqhct2ejHQ0MayUQ8jF -e4l3bya0IbAnY+BQgKNcqKXrKTkw8G0uYLNdokXvwXW2sJ3abH/RCT+Ox/wWHSiJMJG3G6IIhfVL -wRW7G6ewwD22hGORcbU7GO8addo+BGPVUDJdc+PtOZeqNwECgYEAvLJQas3PKLL+qVmO9asvDtCl -tvOuvPFAuZBk6hLm9SSrFt5cCW+rulL8Kx1PxUk0C8LiJV8uwZIqQxE+gY5MkCfvt+xG1yurkywd -SCHOFr0m4gi8+XHvL4fmGzYPgHRi10kepSta5G8USigbcf5fOmw+Upa3qVnwot3CQdxmSfU= ------END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/glide.lock b/glide.lock index b8a06148c..66cbce7b7 100644 --- a/glide.lock +++ b/glide.lock @@ -1,75 +1,71 @@ -hash: a104a522dc2ba982d25101330bbd5d44778565128f0cb09f34061c167921046c -updated: 2016-07-14T08:40:37.798125603-07:00 +hash: 2101b4c83f12c75cbc3cb5be0a7d544dd3a7d21a27ef8a5ee8b014ded8cee034 +updated: 2016-07-27T16:56:51.792310167-03:00 imports: -- name: github.com/amir/raidman - version: 91c20f3f475cab75bb40ad7951d9bbdde357ade7 +- name: github.com/asaskevich/govalidator + version: 593d64559f7600f29581a3ee42177f5dbded27a9 +- name: github.com/boltdb/bolt + version: 5cc10bbbc5c141029940133bb33c9e969512a698 +- name: github.com/gin-gonic/gin + version: 4a6bc4aac4607e253bcda67c8c5bcda693d2388e subpackages: - - proto -- name: github.com/BurntSushi/toml - version: bec2dacf4b590d26237cfebff4471e21ce543494 -- name: github.com/cactus/go-statsd-client - version: 91c326c3f7bd20f0226d3d1c289dd9f8ce28d33d - subpackages: - - statsd -- name: github.com/dgrijalva/jwt-go - version: 01aeca54ebda6e0fbfafd0a524d234159c05ec20 + - binding + - render +- name: github.com/go-openapi/analysis + version: abc9a6171f5bf03ada39aead1aa7fd7bbd44d50f +- name: github.com/go-openapi/errors + version: d24ebc2075bad502fac3a8ae27aa6dd58e1952dc +- name: github.com/go-openapi/jsonpointer + version: 46af16f9f7b149af66e5d1bd010e3574dc06de98 +- name: github.com/go-openapi/jsonreference + version: 13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272 +- name: github.com/go-openapi/loads + version: 18441dfa706d924a39a030ee2c3b1d8d81917b38 +- name: github.com/go-openapi/runtime + version: 11e322eeecc1032d5a0a96c566ed53f2b5c26e22 +- name: github.com/go-openapi/spec + version: e9fab754f5629065e6b7a6100301226545d4477e +- name: github.com/go-openapi/strfmt + version: dfda818c47a4ae5a1dde75ac776f34b2c95de993 +- name: github.com/go-openapi/swag + version: 1d0bd113de87027671077d3c71eb3ac5d7dbba72 +- name: github.com/go-openapi/validate + version: deaf2c9013bc1a7f4c774662259a506ba874d80f - name: github.com/golang/protobuf - version: 874264fbbb43f4d91e999fecb4b40143ed611400 + version: 2402d76f3d41f928c7902a765dfc872356dd3aad subpackages: - proto -- name: github.com/gorilla/context - version: aed02d124ae4a0e94fea4541c8effd05bf0c8296 -- name: github.com/gorilla/mux - version: 9fa818a44c2bf1396a17f9d5a3c0f6dd39d2ff8e -- name: github.com/iron-io/common - version: 7c9faec363c808052742b4c4b84876c4c2172308 +- name: github.com/iron-io/titan + version: 697a5466b096fee73202f5ddccf8213a2357e062 + repo: git@github.com:iron-io/titan.git + vcs: git subpackages: - - httpshutdown - - msgpack - - semaphore - - serverutil -- name: github.com/iron-io/go - version: 1ed3de151aa27db91d6df5dc8c8ce164c833b57c + - jobserver/models +- name: github.com/lib/pq + version: 3cd0097429be7d611bb644ef85b42bfb102ceea4 subpackages: - - common - - common/httpshutdown - - common/msgpack - - common/semaphore - - common/stats -- name: github.com/iron-io/golog - version: 5b80d97af5a2a5d386e7609efb82192ae99a7c67 -- name: github.com/iron-io/iron_go - version: 920c950272e820d9e2562fcbf40303f05564f118 + - oid +- name: github.com/mailru/easyjson + version: 97eee20abef76a0591155412cf0d04f24b05098f subpackages: - - cache - - worker - - api - - config -- name: github.com/mattn/go-colorable - version: 9056b7a9f2d1f2d96498d6d146acd1f9d5ed3d59 -- name: github.com/mattn/go-isatty - version: 56b76bdf51f7708750eac80fa38b952bb9f32639 + - jlexer + - jwriter + - buffer +- name: github.com/manucorporat/sse + version: ee05b128a739a0fb76c7ebd3ae4810c1de808d6d +- name: github.com/PuerkitoBio/purell + version: 1d5d1cfad45d42ec5f81fa8ef23de09cebc6dcc3 +- name: github.com/PuerkitoBio/urlesc + version: 5bd2802263f21d8788851d5305584c82a5c75d7e - name: github.com/Sirupsen/logrus - version: 32055c351ea8b00b96d70f28db48d9840feaf0ec -- name: github.com/vmihailenco/bufio - version: 24e7e48f60fc2d9e99e43c07485d9fff42051e66 -- name: github.com/vrischmann/envconfig - version: 9e6e1c4d3b73427d03118518603bb904d9c55236 + version: a283a10442df8dc09befd873fab202bf8a253d6a - name: golang.org/x/net - version: a728288923b47049b2ce791836767ffbe964a5bd + version: f315505cf3349909cdf013ea56690da34e96a451 subpackages: - - proxy + - context - name: golang.org/x/sys - version: b518c298ac9dc94b6ac0757394f50d10c5dfa25a + version: a646d33e2ee3172a661fc09bca23bb4889a41bc8 subpackages: - unix -- name: gopkg.in/inconshreveable/log15.v2 - version: b105bd37f74e5d9dc7b6ad7806715c7a2b83fd3f - subpackages: - - stack - - term -- name: gopkg.in/mgo.v2 - version: 29cc868a5ca65f401ff318143f9408d02f4799cc - subpackages: - - bson -devImports: [] +- name: gopkg.in/go-playground/validator.v8 + version: c193cecd124b5cc722d7ee5538e945bdb3348435 +testImports: [] diff --git a/glide.yaml b/glide.yaml index 8555146a0..acbe936c9 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,9 +1,14 @@ -package: github.com/iron-io/microgateway +package: github.com/iron-io/functions import: -- package: github.com/gorilla/mux -- package: github.com/iron-io/common -- package: github.com/iron-io/golog -- package: github.com/iron-io/iron_go +- package: github.com/Sirupsen/logrus +- package: github.com/boltdb/bolt +- package: github.com/gin-gonic/gin +- package: github.com/go-openapi/errors +- package: github.com/go-openapi/strfmt +- package: github.com/go-openapi/validate +- package: github.com/iron-io/titan + repo: git@github.com:iron-io/titan.git + vcs: git subpackages: - - cache - - worker + - jobserver/models +- package: github.com/lib/pq diff --git a/hack/api.sh b/hack/api.sh new file mode 100755 index 000000000..b64c7e699 --- /dev/null +++ b/hack/api.sh @@ -0,0 +1,5 @@ +set -ex + +docker run --rm -v "$PWD":/go/src/github.com/iron-io/functions -w /go/src/github.com/iron-io/functions iron/go:dev sh -c 'go build -o functions' +docker build -t iron/functions:latest . +docker run --rm -it -p 8080:8080 -e LOG_LEVEL=debug -v $PWD/bolt.db:/app/bolt.db iron/functions \ No newline at end of file diff --git a/build.sh b/hack/build.sh similarity index 100% rename from build.sh rename to hack/build.sh diff --git a/release.sh b/hack/release.sh similarity index 78% rename from release.sh rename to hack/release.sh index 5cce74f80..39d344eac 100755 --- a/release.sh +++ b/hack/release.sh @@ -15,7 +15,8 @@ perl -i -pe 's/\d+\.\d+\.\K(\d+)/$1+1/e' $version_file version=$(grep -m1 -Eo "[0-9]+\.[0-9]+\.[0-9]+" $version_file) echo "Version: $version" -./build.sh +docker run --rm -v "$PWD":/go/src/github.com/iron-io/functions -w /go/src/github.com/iron-io/functions iron/go:dev sh -c 'go build -o functions' +docker build -t iron/functions:latest . git add -u git commit -m "$service: $version release" diff --git a/proxy/reverser.go b/proxy/reverser.go deleted file mode 100644 index d1e3ba079..000000000 --- a/proxy/reverser.go +++ /dev/null @@ -1,192 +0,0 @@ -/* I wanted to do some stuff to this so had to make a copy. Namely: -- change the Host handling for virtual hosts. -- get errors if the proxy request fails - - -*/ - -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// HTTP reverse proxy handler - -package main - -import ( - "io" - "log" - "net" - "net/http" - "net/url" - "strings" - "sync" - "time" -) - -// onExitFlushLoop is a callback set by tests to detect the state of the -// flushLoop() goroutine. -var onExitFlushLoop func() - -// ReverseProxy is an HTTP Handler that takes an incoming request and -// sends it to another server, proxying the response back to the -// client. -type ReverseProxy struct { - // Director must be a function which modifies - // the request into a new request to be sent - // using Transport. Its response is then copied - // back to the original client unmodified. - Director func(*http.Request) - - // The transport used to perform proxy requests. - // If nil, http.DefaultTransport is used. - Transport http.RoundTripper - - // FlushInterval specifies the flush interval - // to flush to the client while copying the - // response body. - // If zero, no periodic flushing is done. - FlushInterval time.Duration -} - -func singleJoiningSlash(a, b string) string { - aslash := strings.HasSuffix(a, "/") - bslash := strings.HasPrefix(b, "/") - switch { - case aslash && bslash: - return a + b[1:] - case !aslash && !bslash: - return a + "/" + b - } - return a + b -} - -// NewSingleHostReverseProxy returns a new ReverseProxy that rewrites -// URLs to the scheme, host, and base path provided in target. If the -// target's path is "/base" and the incoming request was for "/dir", -// the target request will be for /base/dir. -func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy { - targetQuery := target.RawQuery - director := func(req *http.Request) { - req.URL.Scheme = target.Scheme - req.URL.Host = target.Host - req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path) - if targetQuery == "" || req.URL.RawQuery == "" { - req.URL.RawQuery = targetQuery + req.URL.RawQuery - } else { - req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery - } - } - return &ReverseProxy{Director: director} -} - -func copyHeader(dst, src http.Header) { - for k, vv := range src { - for _, v := range vv { - dst.Add(k, v) - } - } -} - -func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) error { - transport := p.Transport - if transport == nil { - transport = http.DefaultTransport - } - - outreq := new(http.Request) - *outreq = *req // includes shallow copies of maps, but okay - - p.Director(outreq) - outreq.Proto = "HTTP/1.1" - outreq.ProtoMajor = 1 - outreq.ProtoMinor = 1 - outreq.Close = false - - // Remove the connection header to the backend. We want a - // persistent connection, regardless of what the client sent - // to us. This is modifying the same underlying map from req - // (shallow copied above) so we only copy it if necessary. - if outreq.Header.Get("Connection") != "" { - outreq.Header = make(http.Header) - copyHeader(outreq.Header, req.Header) - outreq.Header.Del("Connection") - } - - if clientIp, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { - outreq.Header.Set("X-Forwarded-For", clientIp) - } - - res, err := transport.RoundTrip(outreq) - if err != nil { - log.Printf("http: proxy error: %v", err) - // rw.WriteHeader(http.StatusInternalServerError) - return err - } - defer res.Body.Close() - - copyHeader(rw.Header(), res.Header) - - rw.WriteHeader(res.StatusCode) - p.copyResponse(rw, res.Body) - - return nil - -} - -func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) { - if p.FlushInterval != 0 { - if wf, ok := dst.(writeFlusher); ok { - mlw := &maxLatencyWriter{ - dst: wf, - latency: p.FlushInterval, - done: make(chan bool), - } - go mlw.flushLoop() - defer mlw.stop() - dst = mlw - } - } - - io.Copy(dst, src) -} - -type writeFlusher interface { - io.Writer - http.Flusher -} - -type maxLatencyWriter struct { - dst writeFlusher - latency time.Duration - - lk sync.Mutex // protects Write + Flush - done chan bool -} - -func (m *maxLatencyWriter) Write(p []byte) (int, error) { - m.lk.Lock() - defer m.lk.Unlock() - return m.dst.Write(p) -} - -func (m *maxLatencyWriter) flushLoop() { - t := time.NewTicker(m.latency) - defer t.Stop() - for { - select { - case <-m.done: - if onExitFlushLoop != nil { - onExitFlushLoop() - } - return - case <-t.C: - m.lk.Lock() - m.dst.Flush() - m.lk.Unlock() - } - } - panic("unreached") -} - -func (m *maxLatencyWriter) stop() { m.done <- true } From f6d1f193ac016cbe77c83183af09dc2688ba5cc0 Mon Sep 17 00:00:00 2001 From: Pedro Nasser Date: Wed, 27 Jul 2016 18:32:32 -0300 Subject: [PATCH 6/9] update readme --- README.md | 46 ++++++++++++++-------------------------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index d4d7331b3..494d8d88c 100644 --- a/README.md +++ b/README.md @@ -12,36 +12,9 @@ docker run --rm -it -p 8080:8080 iron/functions This command will quickly start our API using the default database `Bolt` running on `:8080` -Now that we have our API up and running we can quickly create our first function +## Usage -``` -curl -H "Content-Type: application/json" -X POST -d '{ - "name": "MyRoute" - "path": "/myroute" - "image": "iron/hello" -}' http://localhost:8080/v1/apps/myapp/routes -``` - -Done. Now you have our first IronFunctions route ready. - -Now let's test our new route. - -``` -curl http://localhost:8080/r/myapp/myroute -``` - -## Configuring your API - -### Databases - -These are the current databases supported by IronFunctions: - -- [Running with BoltDB](/iron-io/functions/blob/master/docs/database/boltdb.md) -- [Running with Postgres](/iron-io/functions/blob/master/docs/database/postgres.md) - -## API Usage - -### Creating applications +### Creating a application ```sh curl -H "Content-Type: application/json" -X POST -d '{ @@ -49,7 +22,7 @@ curl -H "Content-Type: application/json" -X POST -d '{ }' http://localhost:8080/v1/apps ``` -### Creating routes in a application +### Create a route for your Function Now add routes to the app. First we'll add a route to the output of a docker container: @@ -65,7 +38,7 @@ curl -H "Content-Type: application/json" -X POST -d '{ ``` curl http://localhost:8080/r/myapp/hello -``` +``` ### To pass in data to your function, @@ -89,7 +62,16 @@ And you'll get an ironfunctions.com host: ``` APP_NAME.USER_ID.ironfunctions.com/PATH -``` +``` + +## Configuring your API + +### Databases + +These are the current databases supported by IronFunctions: + +- [Running with BoltDB](/iron-io/functions/blob/master/docs/database/boltdb.md) +- [Running with Postgres](/iron-io/functions/blob/master/docs/database/postgres.md) ## [Examples](/iron-io/functions/blob/master/examples) From eeec3f08d2242df13664a501fe60b74e81d1e84f Mon Sep 17 00:00:00 2001 From: Pedro Nasser Date: Wed, 27 Jul 2016 18:33:57 -0300 Subject: [PATCH 7/9] missing --privileged --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 494d8d88c..a9d5a3200 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ First let's start our IronFunctions API ``` -docker run --rm -it -p 8080:8080 iron/functions +docker run --rm --privileged -it -p 8080:8080 iron/functions ``` This command will quickly start our API using the default database `Bolt` running on `:8080` From fec90c5803d6eb5ab723891202ed8da9574e8565 Mon Sep 17 00:00:00 2001 From: Pedro Nasser Date: Wed, 27 Jul 2016 18:36:05 -0300 Subject: [PATCH 8/9] fixing contributing scripts --- api.sh | 5 +++++ hack/build.sh => build.sh | 0 hack/api.sh | 5 ----- hack/release.sh => release.sh | 3 +-- 4 files changed, 6 insertions(+), 7 deletions(-) create mode 100755 api.sh rename hack/build.sh => build.sh (100%) delete mode 100755 hack/api.sh rename hack/release.sh => release.sh (78%) diff --git a/api.sh b/api.sh new file mode 100755 index 000000000..7562ac6d1 --- /dev/null +++ b/api.sh @@ -0,0 +1,5 @@ +set -ex + +./build.sh + +docker run --rm --privileged -it -p 8080:8080 -e LOG_LEVEL=debug -v $PWD/bolt.db:/app/bolt.db iron/functions \ No newline at end of file diff --git a/hack/build.sh b/build.sh similarity index 100% rename from hack/build.sh rename to build.sh diff --git a/hack/api.sh b/hack/api.sh deleted file mode 100755 index b64c7e699..000000000 --- a/hack/api.sh +++ /dev/null @@ -1,5 +0,0 @@ -set -ex - -docker run --rm -v "$PWD":/go/src/github.com/iron-io/functions -w /go/src/github.com/iron-io/functions iron/go:dev sh -c 'go build -o functions' -docker build -t iron/functions:latest . -docker run --rm -it -p 8080:8080 -e LOG_LEVEL=debug -v $PWD/bolt.db:/app/bolt.db iron/functions \ No newline at end of file diff --git a/hack/release.sh b/release.sh similarity index 78% rename from hack/release.sh rename to release.sh index 39d344eac..5cce74f80 100755 --- a/hack/release.sh +++ b/release.sh @@ -15,8 +15,7 @@ perl -i -pe 's/\d+\.\d+\.\K(\d+)/$1+1/e' $version_file version=$(grep -m1 -Eo "[0-9]+\.[0-9]+\.[0-9]+" $version_file) echo "Version: $version" -docker run --rm -v "$PWD":/go/src/github.com/iron-io/functions -w /go/src/github.com/iron-io/functions iron/go:dev sh -c 'go build -o functions' -docker build -t iron/functions:latest . +./build.sh git add -u git commit -m "$service: $version release" From 63177e9627ccf6ff9f43cc7008119183306351bc Mon Sep 17 00:00:00 2001 From: Pedro Nasser Date: Wed, 27 Jul 2016 18:37:26 -0300 Subject: [PATCH 9/9] removing 'hack/' from CONTRIBUTING.md --- CONTRIBUTING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7d6df9b99..9747e8421 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,17 +8,17 @@ glide install To quick build and run (using default database): ```sh -hack/api.sh +api.sh ``` To build the docker image: ```sh -hack/build.sh +build.sh ``` ## Releasing ```sh -hack/release.sh +release.sh ```