Merge pull request #25 from pedronasser/api-fix

Added wrappers; API fixes and swagger
This commit is contained in:
Travis Reeder
2016-07-29 11:41:49 -07:00
committed by GitHub
31 changed files with 694 additions and 409 deletions

1
.gitignore vendored
View File

@@ -12,6 +12,7 @@ vendor/
/gateway /gateway
/functions /functions
bolt.db bolt.db
.glide/
private.sh private.sh
.env .env

View File

@@ -1,23 +1,24 @@
## Building
## Building/Testing First time or when a dependency changes or when the API changes, run:
```
Build:
```sh
# one time:
glide install 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 ```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 ## Releasing
```sh ```sh
./release.sh release.sh
``` ```

View File

@@ -1,54 +1,53 @@
Note: currently running at: http://gateway.iron.computer:8080/
# IronFunctions # 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 ## Usage
First things first, create an app/service: ### Creating a application
TOOD: App or service??
### Create App
```sh ```sh
iron create app APP_NAME curl -H "Content-Type: application/json" -X POST -d '{
# OR "name":"APP_NAME"
curl -H "Content-Type: application/json" -X POST -d '{"name":"APP_NAME"}' http://localhost:8080/api/v1/apps }' 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: Now add routes to the app. First we'll add a route to the output of a docker container:
```sh ```sh
iron add route myapp /hello iron/hello curl -H "Content-Type: application/json" -X POST -d '{
# OR "name": "hello",
curl -H "Content-Type: application/json" -X POST -d '{"path":"/hello", "image":"iron/hello"}' http://localhost:8080/api/v1/apps/myapp/routes "path":"/hello",
``` "image":"iron/hello"
}' http://localhost:8080/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
``` ```
### Calling your Function ### 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. 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 ```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 ### 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: Simply point to https://functions.iron.io instead of localhost and add your Iron.io Authentication header (TODO: link), like this:
```sh ```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: 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 ### Databases
the route.
## Examples These are the current databases supported by IronFunctions:
TODO: Link to examples in various languages - [Running with BoltDB](/iron-io/functions/blob/master/docs/database/boltdb.md)
TODO: Link to slackbots (easiest way to host slackbots?) - [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
### Logging
Run logspout container on your server.
#### Monitoring
TODO TODO
### Scaling ## Monitoring
TODO
## Scaling
TODO TODO

5
api.sh Executable file
View 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

View File

@@ -5,13 +5,14 @@ import "errors"
type Apps []*App type Apps []*App
var ( var (
ErrAppsCreate = errors.New("Could not create app") ErrAppsCreate = errors.New("Could not create app")
ErrAppsUpdate = errors.New("Could not update app") ErrAppsUpdate = errors.New("Could not update app")
ErrAppsRemoving = errors.New("Could not remove app from datastore") ErrAppsRemoving = errors.New("Could not remove app from datastore")
ErrAppsGet = errors.New("Could not get app from datastore") ErrAppsGet = errors.New("Could not get app from datastore")
ErrAppsList = errors.New("Could not list apps from datastore") ErrAppsList = errors.New("Could not list apps from datastore")
ErrAppsNotFound = errors.New("App not found") ErrAppsNotFound = errors.New("App not found")
ErrAppNothingToUpdate = errors.New("Nothing to update") ErrAppsNothingToUpdate = errors.New("Nothing to update")
ErrAppsMissingNew = errors.New("Missing new application")
) )
type App struct { type App struct {

32
api/models/app_wrapper.go Normal file
View 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
}

View 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
}

View File

@@ -17,6 +17,6 @@ func ApplyAppFilter(app *App, filter *AppFilter) bool {
} }
func ApplyRouteFilter(route *Route, filter *RouteFilter) bool { func ApplyRouteFilter(route *Route, filter *RouteFilter) bool {
return (filter.Path != "" && route.Path == filter.Path) && return (filter.Path == "" || route.Path == filter.Path) &&
(filter.AppName != "" && route.AppName == filter.AppName) (filter.AppName == "" || route.AppName == filter.AppName)
} }

View File

@@ -11,5 +11,5 @@ func (m *Error) Validate() error {
} }
var ( var (
ErrInvalidJSON = errors.New("Could not create app") ErrInvalidJSON = errors.New("Invalid JSON")
) )

View File

@@ -3,27 +3,28 @@ package models
import ( import (
"errors" "errors"
"net/http" "net/http"
apiErrors "github.com/go-openapi/errors"
) )
var ( var (
ErrRoutesCreate = errors.New("Could not create route") ErrRoutesCreate = errors.New("Could not create route")
ErrRoutesUpdate = errors.New("Could not update route") ErrRoutesUpdate = errors.New("Could not update route")
ErrRoutesRemoving = errors.New("Could not remove route from datastore") ErrRoutesRemoving = errors.New("Could not remove route from datastore")
ErrRoutesGet = errors.New("Could not get route from datastore") ErrRoutesGet = errors.New("Could not get route from datastore")
ErrRoutesList = errors.New("Could not list routes from datastore") ErrRoutesList = errors.New("Could not list routes from datastore")
ErrRoutesNotFound = errors.New("Route not found") ErrRoutesNotFound = errors.New("Route not found")
ErrRoutesMissingNew = errors.New("Missing new route")
) )
type Routes []*Route type Routes []*Route
type Route struct { type Route struct {
Name string `json:"name"` Name string `json:"name"`
AppName string `json:"appname"` AppName string `json:"appname"`
Path string `json:"path"` Path string `json:"path"`
Image string `json:"image"` Image string `json:"image"`
Type string `json:"type,omitempty"` Headers http.Header `json:"headers,omitempty"`
ContainerPath string `json:"container_path,omitempty"`
Headers http.Header `json:"headers,omitempty"`
} }
var ( var (
@@ -34,20 +35,26 @@ var (
) )
func (r *Route) Validate() error { func (r *Route) Validate() error {
var res []error
if r.Name == "" { if r.Name == "" {
return ErrRoutesValidationName res = append(res, ErrRoutesValidationAppName)
} }
if r.Image == "" { if r.Image == "" {
return ErrRoutesValidationImage res = append(res, ErrRoutesValidationImage)
} }
if r.AppName == "" { if r.AppName == "" {
return ErrRoutesValidationAppName res = append(res, ErrRoutesValidationAppName)
} }
if r.Path == "" { if r.Path == "" {
return ErrRoutesValidationPath res = append(res, ErrRoutesValidationPath)
}
if len(res) > 0 {
return apiErrors.CompositeValidationError(res...)
} }
return nil return nil

View 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
}

View 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
}

View File

@@ -17,8 +17,6 @@ CREATE TABLE IF NOT EXISTS routes (
path text NOT NULL, path text NOT NULL,
app_name character varying(256) NOT NULL, app_name character varying(256) NOT NULL,
image 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 headers text NOT NULL
);` );`
@@ -26,7 +24,7 @@ const appsTableCreate = `CREATE TABLE IF NOT EXISTS apps (
name character varying(256) NOT NULL PRIMARY KEY 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 { type rowScanner interface {
Scan(dest ...interface{}) error Scan(dest ...interface{}) error
@@ -162,22 +160,18 @@ func (ds *PostgresDatastore) StoreRoute(route *models.Route) (*models.Route, err
_, err = ds.db.Exec(` _, err = ds.db.Exec(`
INSERT INTO routes ( INSERT INTO routes (
name, app_name, path, image, name, app_name, path, image,
type, container_path, headers headers
) )
VALUES ($1, $2, $3, $4, $5, $6, $7) VALUES ($1, $2, $3, $4, $5, $6, $7)
ON CONFLICT (name) DO UPDATE SET ON CONFLICT (name) DO UPDATE SET
path = $3, path = $3,
image = $4, image = $4,
type = $5, headers = $5;
container_path = $6,
headers = $7;
`, `,
route.Name, route.Name,
route.AppName, route.AppName,
route.Path, route.Path,
route.Image, route.Image,
route.Type,
route.ContainerPath,
headers, headers,
) )
@@ -206,8 +200,6 @@ func scanRoute(scanner rowScanner, route *models.Route) error {
&route.Path, &route.Path,
&route.AppName, &route.AppName,
&route.Image, &route.Image,
&route.Type,
&route.ContainerPath,
&headerStr, &headerStr,
) )

View File

@@ -12,22 +12,28 @@ func handleAppCreate(c *gin.Context) {
store := c.MustGet("store").(models.Datastore) store := c.MustGet("store").(models.Datastore)
log := c.MustGet("log").(logrus.FieldLogger) log := c.MustGet("log").(logrus.FieldLogger)
app := &models.App{} wapp := &models.AppWrapper{}
err := c.BindJSON(app) err := c.BindJSON(wapp)
if err != nil { if err != nil {
log.WithError(err).Debug(models.ErrInvalidJSON) log.WithError(err).Debug(models.ErrInvalidJSON)
c.JSON(http.StatusBadRequest, simpleError(models.ErrInvalidJSON)) c.JSON(http.StatusBadRequest, simpleError(models.ErrInvalidJSON))
return 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) log.Error(err)
c.JSON(http.StatusInternalServerError, simpleError(err)) c.JSON(http.StatusInternalServerError, simpleError(err))
return return
} }
app, err = store.StoreApp(app) app, err := store.StoreApp(wapp.App)
if err != nil { if err != nil {
log.WithError(err).Debug(models.ErrAppsCreate) log.WithError(err).Debug(models.ErrAppsCreate)
c.JSON(http.StatusInternalServerError, simpleError(models.ErrAppsCreate)) c.JSON(http.StatusInternalServerError, simpleError(models.ErrAppsCreate))

View File

@@ -40,5 +40,5 @@ func handleAppGet(c *gin.Context) {
app.Routes = routes app.Routes = routes
c.JSON(http.StatusOK, app) c.JSON(http.StatusOK, &models.AppWrapper{app})
} }

View File

@@ -21,5 +21,5 @@ func handleAppList(c *gin.Context) {
return return
} }
c.JSON(http.StatusOK, apps) c.JSON(http.StatusOK, &models.AppsWrapper{apps})
} }

View File

@@ -21,14 +21,5 @@ func handleAppUpdate(c *gin.Context) {
return return
} }
// app.Name = c.Param("app") c.JSON(http.StatusOK, simpleError(models.ErrAppsNothingToUpdate))
// 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))
} }

View File

@@ -15,7 +15,7 @@ func Start(engine *gin.Engine) {
v1.POST("/apps", handleAppCreate) v1.POST("/apps", handleAppCreate)
v1.GET("/apps/:app", handleAppGet) v1.GET("/apps/:app", handleAppGet)
v1.POST("/apps/:app", handleAppUpdate) v1.PUT("/apps/:app", handleAppUpdate)
v1.DELETE("/apps/:app", handleAppDestroy) v1.DELETE("/apps/:app", handleAppDestroy)
apps := v1.Group("/apps/:app") apps := v1.Group("/apps/:app")

View File

@@ -16,11 +16,17 @@ func handleRouteCreate(c *gin.Context) {
err := c.BindJSON(route) err := c.BindJSON(route)
if err != nil { if err != nil {
log.WithError(err).Debug(models.ErrInvalidJSON) log.WithError(err).Error(models.ErrInvalidJSON)
c.JSON(http.StatusBadRequest, simpleError(models.ErrInvalidJSON)) c.JSON(http.StatusBadRequest, simpleError(models.ErrInvalidJSON))
return return
} }
if route == nil {
log.WithError(err).Error(models.ErrInvalidJSON)
c.JSON(http.StatusBadRequest, simpleError(models.ErrRoutesMissingNew))
return
}
route.AppName = c.Param("app") route.AppName = c.Param("app")
if err := route.Validate(); err != nil { if err := route.Validate(); err != nil {
@@ -46,7 +52,7 @@ func handleRouteCreate(c *gin.Context) {
route, err = store.StoreRoute(route) route, err = store.StoreRoute(route)
if err != nil { if err != nil {
log.WithError(err).Debug(models.ErrRoutesCreate) log.WithError(err).Error(models.ErrRoutesCreate)
c.JSON(http.StatusInternalServerError, simpleError(models.ErrRoutesCreate)) c.JSON(http.StatusInternalServerError, simpleError(models.ErrRoutesCreate))
return return
} }

View File

@@ -24,5 +24,5 @@ func handleRouteGet(c *gin.Context) {
log.WithFields(logrus.Fields{"route": route}).Debug("Got route") log.WithFields(logrus.Fields{"route": route}).Debug("Got route")
c.JSON(http.StatusOK, route) c.JSON(http.StatusOK, &models.RouteWrapper{route})
} }

View File

@@ -27,5 +27,5 @@ func handleRouteList(c *gin.Context) {
log.WithFields(logrus.Fields{"routes": routes}).Debug("Got routes") log.WithFields(logrus.Fields{"routes": routes}).Debug("Got routes")
c.JSON(http.StatusOK, routes) c.JSON(http.StatusOK, &models.RoutesWrapper{Routes: routes})
} }

View File

@@ -12,19 +12,19 @@ func handleRouteUpdate(c *gin.Context) {
store := c.MustGet("store").(models.Datastore) store := c.MustGet("store").(models.Datastore)
log := c.MustGet("log").(logrus.FieldLogger) log := c.MustGet("log").(logrus.FieldLogger)
route := &models.Route{} wroute := &models.RouteWrapper{}
appName := c.Param("app") appName := c.Param("app")
err := c.BindJSON(route) err := c.BindJSON(wroute)
if err != nil { if err != nil {
log.WithError(err).Debug(models.ErrInvalidJSON) log.WithError(err).Debug(models.ErrInvalidJSON)
c.JSON(http.StatusBadRequest, simpleError(models.ErrInvalidJSON)) c.JSON(http.StatusBadRequest, simpleError(models.ErrInvalidJSON))
return return
} }
route.AppName = appName wroute.Route.AppName = appName
route, err = store.StoreRoute(route) route, err := store.StoreRoute(wroute.Route)
if err != nil { if err != nil {
log.WithError(err).Debug(models.ErrAppsCreate) log.WithError(err).Debug(models.ErrAppsCreate)
c.JSON(http.StatusInternalServerError, simpleError(models.ErrAppsCreate)) c.JSON(http.StatusInternalServerError, simpleError(models.ErrAppsCreate))

324
api/swagger.yml Normal file
View 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
View File

11
docs/database/boltdb.md Normal file
View 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
View 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
```

View File

@@ -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

View File

@@ -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
View File

@@ -1,75 +1,71 @@
hash: a104a522dc2ba982d25101330bbd5d44778565128f0cb09f34061c167921046c hash: 2101b4c83f12c75cbc3cb5be0a7d544dd3a7d21a27ef8a5ee8b014ded8cee034
updated: 2016-07-14T08:40:37.798125603-07:00 updated: 2016-07-27T16:56:51.792310167-03:00
imports: imports:
- name: github.com/amir/raidman - name: github.com/asaskevich/govalidator
version: 91c20f3f475cab75bb40ad7951d9bbdde357ade7 version: 593d64559f7600f29581a3ee42177f5dbded27a9
- name: github.com/boltdb/bolt
version: 5cc10bbbc5c141029940133bb33c9e969512a698
- name: github.com/gin-gonic/gin
version: 4a6bc4aac4607e253bcda67c8c5bcda693d2388e
subpackages: subpackages:
- proto - binding
- name: github.com/BurntSushi/toml - render
version: bec2dacf4b590d26237cfebff4471e21ce543494 - name: github.com/go-openapi/analysis
- name: github.com/cactus/go-statsd-client version: abc9a6171f5bf03ada39aead1aa7fd7bbd44d50f
version: 91c326c3f7bd20f0226d3d1c289dd9f8ce28d33d - name: github.com/go-openapi/errors
subpackages: version: d24ebc2075bad502fac3a8ae27aa6dd58e1952dc
- statsd - name: github.com/go-openapi/jsonpointer
- name: github.com/dgrijalva/jwt-go version: 46af16f9f7b149af66e5d1bd010e3574dc06de98
version: 01aeca54ebda6e0fbfafd0a524d234159c05ec20 - 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 - name: github.com/golang/protobuf
version: 874264fbbb43f4d91e999fecb4b40143ed611400 version: 2402d76f3d41f928c7902a765dfc872356dd3aad
subpackages: subpackages:
- proto - proto
- name: github.com/gorilla/context - name: github.com/iron-io/titan
version: aed02d124ae4a0e94fea4541c8effd05bf0c8296 version: 697a5466b096fee73202f5ddccf8213a2357e062
- name: github.com/gorilla/mux repo: git@github.com:iron-io/titan.git
version: 9fa818a44c2bf1396a17f9d5a3c0f6dd39d2ff8e vcs: git
- name: github.com/iron-io/common
version: 7c9faec363c808052742b4c4b84876c4c2172308
subpackages: subpackages:
- httpshutdown - jobserver/models
- msgpack - name: github.com/lib/pq
- semaphore version: 3cd0097429be7d611bb644ef85b42bfb102ceea4
- serverutil
- name: github.com/iron-io/go
version: 1ed3de151aa27db91d6df5dc8c8ce164c833b57c
subpackages: subpackages:
- common - oid
- common/httpshutdown - name: github.com/mailru/easyjson
- common/msgpack version: 97eee20abef76a0591155412cf0d04f24b05098f
- common/semaphore
- common/stats
- name: github.com/iron-io/golog
version: 5b80d97af5a2a5d386e7609efb82192ae99a7c67
- name: github.com/iron-io/iron_go
version: 920c950272e820d9e2562fcbf40303f05564f118
subpackages: subpackages:
- cache - jlexer
- worker - jwriter
- api - buffer
- config - name: github.com/manucorporat/sse
- name: github.com/mattn/go-colorable version: ee05b128a739a0fb76c7ebd3ae4810c1de808d6d
version: 9056b7a9f2d1f2d96498d6d146acd1f9d5ed3d59 - name: github.com/PuerkitoBio/purell
- name: github.com/mattn/go-isatty version: 1d5d1cfad45d42ec5f81fa8ef23de09cebc6dcc3
version: 56b76bdf51f7708750eac80fa38b952bb9f32639 - name: github.com/PuerkitoBio/urlesc
version: 5bd2802263f21d8788851d5305584c82a5c75d7e
- name: github.com/Sirupsen/logrus - name: github.com/Sirupsen/logrus
version: 32055c351ea8b00b96d70f28db48d9840feaf0ec version: a283a10442df8dc09befd873fab202bf8a253d6a
- name: github.com/vmihailenco/bufio
version: 24e7e48f60fc2d9e99e43c07485d9fff42051e66
- name: github.com/vrischmann/envconfig
version: 9e6e1c4d3b73427d03118518603bb904d9c55236
- name: golang.org/x/net - name: golang.org/x/net
version: a728288923b47049b2ce791836767ffbe964a5bd version: f315505cf3349909cdf013ea56690da34e96a451
subpackages: subpackages:
- proxy - context
- name: golang.org/x/sys - name: golang.org/x/sys
version: b518c298ac9dc94b6ac0757394f50d10c5dfa25a version: a646d33e2ee3172a661fc09bca23bb4889a41bc8
subpackages: subpackages:
- unix - unix
- name: gopkg.in/inconshreveable/log15.v2 - name: gopkg.in/go-playground/validator.v8
version: b105bd37f74e5d9dc7b6ad7806715c7a2b83fd3f version: c193cecd124b5cc722d7ee5538e945bdb3348435
subpackages: testImports: []
- stack
- term
- name: gopkg.in/mgo.v2
version: 29cc868a5ca65f401ff318143f9408d02f4799cc
subpackages:
- bson
devImports: []

View File

@@ -1,9 +1,14 @@
package: github.com/iron-io/microgateway package: github.com/iron-io/functions
import: import:
- package: github.com/gorilla/mux - package: github.com/Sirupsen/logrus
- package: github.com/iron-io/common - package: github.com/boltdb/bolt
- package: github.com/iron-io/golog - package: github.com/gin-gonic/gin
- package: github.com/iron-io/iron_go - 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: subpackages:
- cache - jobserver/models
- worker - package: github.com/lib/pq

View File

@@ -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 }