mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
Merge pull request #25 from pedronasser/api-fix
Added wrappers; API fixes and swagger
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,6 +12,7 @@ vendor/
|
||||
/gateway
|
||||
/functions
|
||||
bolt.db
|
||||
.glide/
|
||||
|
||||
private.sh
|
||||
.env
|
||||
|
||||
@@ -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
|
||||
api.sh
|
||||
```
|
||||
|
||||
To build the docker image:
|
||||
|
||||
```sh
|
||||
build.sh
|
||||
```
|
||||
|
||||
## Releasing
|
||||
|
||||
```sh
|
||||
./release.sh
|
||||
release.sh
|
||||
```
|
||||
|
||||
82
README.md
82
README.md
@@ -1,54 +1,53 @@
|
||||
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 --privileged -it -p 8080:8080 iron/functions
|
||||
```
|
||||
|
||||
This command will quickly start our API using the default database `Bolt` running on `:8080`
|
||||
|
||||
## Usage
|
||||
|
||||
First things first, create an app/service:
|
||||
TOOD: App or service??
|
||||
|
||||
### Create App
|
||||
### Creating a application
|
||||
|
||||
```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
|
||||
### 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:
|
||||
|
||||
```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 +55,35 @@ 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
|
||||
## Configuring your API
|
||||
|
||||
Tag your images with a version, eg `treeder/guppy:0.0.5` then use that including the tag and update
|
||||
the route.
|
||||
### Databases
|
||||
|
||||
## Examples
|
||||
These are the current databases supported by IronFunctions:
|
||||
|
||||
TODO: Link to examples in various languages
|
||||
TODO: Link to slackbots (easiest way to host slackbots?)
|
||||
- [Running with BoltDB](/iron-io/functions/blob/master/docs/database/boltdb.md)
|
||||
- [Running with Postgres](/iron-io/functions/blob/master/docs/database/postgres.md)
|
||||
|
||||
## Operations
|
||||
## [Examples](/iron-io/functions/blob/master/examples)
|
||||
|
||||
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
|
||||
|
||||
|
||||
5
api.sh
Executable file
5
api.sh
Executable file
@@ -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
|
||||
@@ -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 {
|
||||
|
||||
32
api/models/app_wrapper.go
Normal file
32
api/models/app_wrapper.go
Normal file
@@ -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
|
||||
}
|
||||
34
api/models/apps_wrapper.go
Normal file
34
api/models/apps_wrapper.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -11,5 +11,5 @@ func (m *Error) Validate() error {
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidJSON = errors.New("Could not create app")
|
||||
ErrInvalidJSON = errors.New("Invalid JSON")
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
31
api/models/route_wrapper.go
Normal file
31
api/models/route_wrapper.go
Normal file
@@ -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
|
||||
}
|
||||
36
api/models/routes_wrapper.go
Normal file
36
api/models/routes_wrapper.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -40,5 +40,5 @@ func handleAppGet(c *gin.Context) {
|
||||
|
||||
app.Routes = routes
|
||||
|
||||
c.JSON(http.StatusOK, app)
|
||||
c.JSON(http.StatusOK, &models.AppWrapper{app})
|
||||
}
|
||||
|
||||
@@ -21,5 +21,5 @@ func handleAppList(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, apps)
|
||||
c.JSON(http.StatusOK, &models.AppsWrapper{apps})
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -16,11 +16,17 @@ func handleRouteCreate(c *gin.Context) {
|
||||
|
||||
err := c.BindJSON(route)
|
||||
if err != nil {
|
||||
log.WithError(err).Debug(models.ErrInvalidJSON)
|
||||
log.WithError(err).Error(models.ErrInvalidJSON)
|
||||
c.JSON(http.StatusBadRequest, simpleError(models.ErrInvalidJSON))
|
||||
return
|
||||
}
|
||||
|
||||
if route == nil {
|
||||
log.WithError(err).Error(models.ErrInvalidJSON)
|
||||
c.JSON(http.StatusBadRequest, simpleError(models.ErrRoutesMissingNew))
|
||||
return
|
||||
}
|
||||
|
||||
route.AppName = c.Param("app")
|
||||
|
||||
if err := route.Validate(); err != nil {
|
||||
@@ -46,7 +52,7 @@ func handleRouteCreate(c *gin.Context) {
|
||||
|
||||
route, err = store.StoreRoute(route)
|
||||
if err != nil {
|
||||
log.WithError(err).Debug(models.ErrRoutesCreate)
|
||||
log.WithError(err).Error(models.ErrRoutesCreate)
|
||||
c.JSON(http.StatusInternalServerError, simpleError(models.ErrRoutesCreate))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
324
api/swagger.yml
Normal file
324
api/swagger.yml
Normal file
@@ -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'
|
||||
0
docs/api.md
Normal file
0
docs/api.md
Normal file
11
docs/database/boltdb.md
Normal file
11
docs/database/boltdb.md
Normal file
@@ -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
|
||||
```
|
||||
34
docs/database/postgres.md
Normal file
34
docs/database/postgres.md
Normal file
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
23
gateway.pem
23
gateway.pem
@@ -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-----
|
||||
118
glide.lock
generated
118
glide.lock
generated
@@ -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: []
|
||||
|
||||
19
glide.yaml
19
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
|
||||
|
||||
@@ -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 }
|
||||
Reference in New Issue
Block a user