improv api, datastore, postgres, runner

This commit is contained in:
Pedro Nasser
2016-07-21 21:18:02 -03:00
parent 154fd82b68
commit 5a13e2c0cc
10 changed files with 215 additions and 127 deletions

View File

@@ -2,20 +2,32 @@ package models
import "errors" 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")
) )
type App struct { type App struct {
Name string `json:"name"` Name string `json:"name"`
Routes Routes `json:"routes"` Routes Routes `json:"routes,omitempty"`
}
var (
ErrAppsValidationName = errors.New("Missing app name")
)
func (a *App) Validate() error {
if a.Name == "" {
return ErrAppsValidationName
}
return nil
} }
type AppFilter struct { type AppFilter struct {

View File

@@ -14,16 +14,43 @@ var (
ErrRoutesNotFound = errors.New("Route not found") ErrRoutesNotFound = errors.New("Route not found")
) )
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"` Type string `json:"type,omitempty"`
ContainerPath string `json:"container_path"` ContainerPath string `json:"container_path,omitempty"`
Headers http.Header `json:"headers"` Headers http.Header `json:"headers,omitempty"`
}
var (
ErrRoutesValidationName = errors.New("Missing route Name")
ErrRoutesValidationImage = errors.New("Missing route Image")
ErrRoutesValidationAppName = errors.New("Missing route AppName")
ErrRoutesValidationPath = errors.New("Missing route Path")
)
func (r *Route) Validate() error {
if r.Name == "" {
return ErrRoutesValidationName
}
if r.Image == "" {
return ErrRoutesValidationImage
}
if r.AppName == "" {
return ErrRoutesValidationAppName
}
if r.Path == "" {
return ErrRoutesValidationPath
}
return nil
} }
type RouteFilter struct { type RouteFilter struct {

View File

@@ -87,6 +87,10 @@ func DockerRun(route *models.Route, c *gin.Context) error {
// log.WithField("payload", "---"+string(payload)+"---").Infoln("incoming request") // log.WithField("payload", "---"+string(payload)+"---").Infoln("incoming request")
// log.WithField("image", image).Infoln("About to run using this image") // log.WithField("image", image).Infoln("About to run using this image")
for k, v := range route.Headers {
c.Header(k, v[0])
}
// TODO: swap all this out with Titan's running via API // TODO: swap all this out with Titan's running via API
cmd := exec.Command("docker", "run", "--rm", "-i", "-e", fmt.Sprintf("PAYLOAD=%v", string(payload)), image) cmd := exec.Command("docker", "run", "--rm", "-i", "-e", fmt.Sprintf("PAYLOAD=%v", string(payload)), image)
stdout, err := cmd.StdoutPipe() stdout, err := cmd.StdoutPipe()

View File

@@ -33,7 +33,7 @@ func New(url *url.URL) (models.Datastore, error) {
log.WithError(err).Errorln("Error on bolt.Open") log.WithError(err).Errorln("Error on bolt.Open")
return nil, err return nil, err
} }
bucketPrefix := "fns-" bucketPrefix := "funcs-"
if url.Query()["bucket"] != nil { if url.Query()["bucket"] != nil {
bucketPrefix = url.Query()["bucket"][0] bucketPrefix = url.Query()["bucket"][0]
} }

View File

@@ -11,46 +11,31 @@ import (
_ "github.com/lib/pq" _ "github.com/lib/pq"
) )
const routesTableCreate = `CREATE TABLE IF NOT EXISTS routes ( const routesTableCreate = `
name character varying(256) NOT NULL PRIMARY KEY, CREATE TABLE IF NOT EXISTS routes (
name character varying(256) NOT NULL PRIMARY KEY,
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, type character varying(256) NOT NULL,
container_path text NOT NULL, container_path text NOT NULL,
headers text NOT NULL, headers text NOT NULL
);` );`
const appsTableCreate = `CREATE TABLE IF NOT EXISTS apps ( 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 path, app_name, image, type, container_path, headers FROM routes` const routeSelector = `SELECT name, path, app_name, image, type, container_path, headers FROM routes`
// Tries to read in properties into `route` from `scanner`. Bubbles up errors.
// Capture sql.Row and sql.Rows
type rowScanner interface { type rowScanner interface {
Scan(dest ...interface{}) error Scan(dest ...interface{}) error
} }
// Capture sql.DB and sql.Tx
type rowQuerier interface { type rowQuerier interface {
QueryRow(query string, args ...interface{}) *sql.Row QueryRow(query string, args ...interface{}) *sql.Row
} }
func scanRoute(scanner rowScanner, route *models.Route) error {
err := scanner.Scan(
&route.Name,
&route.Path,
&route.AppName,
&route.Image,
&route.Type,
&route.ContainerPath,
&route.Headers,
)
return err
}
type PostgresDatastore struct { type PostgresDatastore struct {
db *sql.DB db *sql.DB
} }
@@ -84,6 +69,86 @@ func New(url *url.URL) (models.Datastore, error) {
return pg, nil return pg, nil
} }
func (ds *PostgresDatastore) StoreApp(app *models.App) (*models.App, error) {
_, err := ds.db.Exec(`
INSERT INTO apps (name)
VALUES ($1)
RETURNING name;
`, app.Name)
if err != nil {
return nil, err
}
return app, nil
}
func (ds *PostgresDatastore) RemoveApp(appName string) error {
_, err := ds.db.Exec(`
DELETE FROM apps
WHERE name = $1
`, appName)
if err != nil {
return err
}
return nil
}
func (ds *PostgresDatastore) GetApp(name string) (*models.App, error) {
row := ds.db.QueryRow("SELECT name FROM apps WHERE name=$1", name)
var resName string
err := row.Scan(&resName)
res := &models.App{
Name: resName,
}
if err != nil {
return nil, err
}
return res, nil
}
func scanApp(scanner rowScanner, app *models.App) error {
err := scanner.Scan(
&app.Name,
)
return err
}
func (ds *PostgresDatastore) GetApps(filter *models.AppFilter) ([]*models.App, error) {
res := []*models.App{}
rows, err := ds.db.Query(`
SELECT DISTINCT *
FROM apps`,
)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var app models.App
err := scanApp(rows, &app)
if err != nil {
return nil, err
}
res = append(res, &app)
}
if err := rows.Err(); err != nil {
return nil, err
}
return res, nil
}
func (ds *PostgresDatastore) StoreRoute(route *models.Route) (*models.Route, error) { func (ds *PostgresDatastore) StoreRoute(route *models.Route) (*models.Route, error) {
var headers string var headers string
@@ -94,12 +159,19 @@ func (ds *PostgresDatastore) StoreRoute(route *models.Route) (*models.Route, err
headers = string(hbyte) headers = string(hbyte)
err = ds.db.QueryRow(` _, err = ds.db.Exec(`
INSERT INTO routes ( INSERT INTO routes (
name, app_name, path, image, name, app_name, path, image,
type, container_path, headers type, container_path, headers
) )
VALUES ($1, $2, $3, $4, $5, $6, $7)`, 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;
`,
route.Name, route.Name,
route.AppName, route.AppName,
route.Path, route.Path,
@@ -107,7 +179,8 @@ func (ds *PostgresDatastore) StoreRoute(route *models.Route) (*models.Route, err
route.Type, route.Type,
route.ContainerPath, route.ContainerPath,
headers, headers,
).Scan(nil) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -115,17 +188,38 @@ func (ds *PostgresDatastore) StoreRoute(route *models.Route) (*models.Route, err
} }
func (ds *PostgresDatastore) RemoveRoute(appName, routeName string) error { func (ds *PostgresDatastore) RemoveRoute(appName, routeName string) error {
err := ds.db.QueryRow(` _, err := ds.db.Exec(`
DELETE FROM routes DELETE FROM routes
WHERE name = $1`, WHERE name = $1
routeName, `, routeName)
).Scan(nil)
if err != nil { if err != nil {
return err return err
} }
return nil return nil
} }
func scanRoute(scanner rowScanner, route *models.Route) error {
var headerStr string
err := scanner.Scan(
&route.Name,
&route.Path,
&route.AppName,
&route.Image,
&route.Type,
&route.ContainerPath,
&headerStr,
)
if headerStr == "" {
return models.ErrRoutesNotFound
}
err = json.Unmarshal([]byte(headerStr), &route.Headers)
return err
}
func getRoute(qr rowQuerier, routeName string) (*models.Route, error) { func getRoute(qr rowQuerier, routeName string) (*models.Route, error) {
var route models.Route var route models.Route
@@ -144,7 +238,6 @@ func (ds *PostgresDatastore) GetRoute(appName, routeName string) (*models.Route,
return getRoute(ds.db, routeName) return getRoute(ds.db, routeName)
} }
// TODO: Add pagination
func (ds *PostgresDatastore) GetRoutes(filter *models.RouteFilter) ([]*models.Route, error) { func (ds *PostgresDatastore) GetRoutes(filter *models.RouteFilter) ([]*models.Route, error) {
res := []*models.Route{} res := []*models.Route{}
filterQuery := buildFilterQuery(filter) filterQuery := buildFilterQuery(filter)
@@ -169,79 +262,6 @@ func (ds *PostgresDatastore) GetRoutes(filter *models.RouteFilter) ([]*models.Ro
return res, nil return res, nil
} }
func (ds *PostgresDatastore) GetApp(name string) (*models.App, error) {
row := ds.db.QueryRow("SELECT * FROM groups WHERE name=$1", name)
var resName string
err := row.Scan(&resName)
res := &models.App{
Name: resName,
}
if err != nil {
return nil, err
}
return res, nil
}
func (ds *PostgresDatastore) GetApps(filter *models.AppFilter) ([]*models.App, error) {
res := []*models.App{}
rows, err := ds.db.Query(`
SELECT DISTINCT *
FROM apps
ORDER BY name`,
)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var app models.App
err := rows.Scan(&app)
if err != nil {
return nil, err
}
res = append(res, &app)
}
if err := rows.Err(); err != nil {
return nil, err
}
return res, nil
}
func (ds *PostgresDatastore) StoreApp(app *models.App) (*models.App, error) {
err := ds.db.QueryRow(`
INSERT INTO apps (name)
VALUES ($1)
`, app.Name).Scan(&app.Name)
// ON CONFLICT (name) DO UPDATE SET created_at = $2;
if err != nil {
return nil, err
}
return app, nil
}
func (ds *PostgresDatastore) RemoveApp(appName string) error {
err := ds.db.QueryRow(`
DELETE FROM apps
WHERE name = $1
`, appName).Scan(nil)
if err != nil {
return err
}
return nil
}
func buildFilterQuery(filter *models.RouteFilter) string { func buildFilterQuery(filter *models.RouteFilter) string {
filterQuery := "" filterQuery := ""

View File

@@ -21,6 +21,12 @@ func handleAppCreate(c *gin.Context) {
return return
} }
if err := app.Validate(); err != nil {
log.Error(err)
c.JSON(http.StatusInternalServerError, simpleError(err))
return
}
app, err = store.StoreApp(app) app, err = store.StoreApp(app)
if err != nil { if err != nil {
log.WithError(err).Debug(models.ErrAppsCreate) log.WithError(err).Debug(models.ErrAppsCreate)

View File

@@ -27,5 +27,18 @@ func handleAppGet(c *gin.Context) {
return return
} }
filter := &models.RouteFilter{
AppName: appName,
}
routes, err := store.GetRoutes(filter)
if err != nil {
log.WithError(err).Error(models.ErrRoutesGet)
c.JSON(http.StatusInternalServerError, simpleError(models.ErrRoutesGet))
return
}
app.Routes = routes
c.JSON(http.StatusOK, app) c.JSON(http.StatusOK, app)
} }

View File

@@ -9,7 +9,7 @@ import (
) )
func handleAppUpdate(c *gin.Context) { func handleAppUpdate(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{} app := &models.App{}
@@ -21,14 +21,14 @@ func handleAppUpdate(c *gin.Context) {
return return
} }
app.Name = c.Param("app") // app.Name = c.Param("app")
app, err = store.StoreApp(app) // app, err = store.StoreApp(app)
if err != nil { // if err != nil {
log.WithError(err).Debug(models.ErrAppsUpdate) // log.WithError(err).Debug(models.ErrAppsUpdate)
c.JSON(http.StatusInternalServerError, simpleError(models.ErrAppsUpdate)) // c.JSON(http.StatusInternalServerError, simpleError(models.ErrAppsUpdate))
return // return
} // }
c.JSON(http.StatusOK, app) c.JSON(http.StatusOK, simpleError(models.ErrAppNothingToUpdate))
} }

View File

@@ -23,6 +23,12 @@ func handleRouteCreate(c *gin.Context) {
route.AppName = c.Param("app") route.AppName = c.Param("app")
if err := route.Validate(); err != nil {
log.Error(err)
c.JSON(http.StatusInternalServerError, simpleError(err))
return
}
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).Debug(models.ErrRoutesCreate)

View File

@@ -34,7 +34,7 @@ func extractFields(c *gin.Context) logrus.Fields {
func (s *Server) Start() { func (s *Server) Start() {
if s.cfg.DatabaseURL == "" { if s.cfg.DatabaseURL == "" {
cwd, _ := os.Getwd() cwd, _ := os.Getwd()
s.cfg.DatabaseURL = fmt.Sprintf("bolt://%s/bolt.db?bucket=fns", cwd) s.cfg.DatabaseURL = fmt.Sprintf("bolt://%s/bolt.db?bucket=funcs", cwd)
} }
ds, err := datastore.New(s.cfg.DatabaseURL) ds, err := datastore.New(s.cfg.DatabaseURL)