mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
mask models.Call blank fields in api, sqlx
sqlx has nice facilities for using structs to do queries and using their fields, so decided to move us all over to this. now when you take a look at models.Call it's really obvious what's in db and what's not. added omitempty to some json fields that were bleeding through api too. deletes a lot of code in the sql package for scanning and made some queries use struct based sqlx methods now which seem easier to read than what we previously had. moves all json stuff into sql.Valuer and sql.Scanner methods in models/config.go, these are the only 2 types that ever need this. sadly, sqlx would have done this marshaling for us, but to keep compat, I added json. we can do some migrations later maybe for a more efficient encoding, but did not want to fuss with it today. it seems like we should probably aim to keep models.Call as small as possible in the db as there will be a lot of them. interestingly, most functions platforms I looked at do not seem to expose this kind of information that I could find. so, i think only having timestamps, status, id, app, path and maybe docker stats is really all that should be in here (agree w/ Denys on 284 as these and logs will end up taking up most db space in prod. notably, payload, headers, and env vars could be extremely large and in the general case they are always a copy of the routes (this breaks apart when routes are updated, which would be useful considering we don't have versioning -- versioning may be cheaper). removed unused field in apps too this is lined up behind #349 so that I could use the tests... closes #345 closes #142 closes #284
This commit is contained in:
@@ -4,7 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@@ -440,7 +439,7 @@ func Test(t *testing.T, dsf func() models.Datastore) {
|
|||||||
var expected models.Route = *testRoute
|
var expected models.Route = *testRoute
|
||||||
if !reflect.DeepEqual(*route, expected) {
|
if !reflect.DeepEqual(*route, expected) {
|
||||||
t.Log(buf.String())
|
t.Log(buf.String())
|
||||||
t.Fatalf("Test InsertApp: expected to insert:\n%v\nbut got:\n%v", expected, route)
|
t.Fatalf("Test InsertApp: expected to insert:\n%v\nbut got:\n%v", expected, *route)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,7 +455,7 @@ func Test(t *testing.T, dsf func() models.Datastore) {
|
|||||||
"SECOND": "2",
|
"SECOND": "2",
|
||||||
"THIRD": "3",
|
"THIRD": "3",
|
||||||
},
|
},
|
||||||
Headers: http.Header{
|
Headers: models.Headers{
|
||||||
"First": []string{"test"},
|
"First": []string{"test"},
|
||||||
"Second": []string{"test", "test"},
|
"Second": []string{"test", "test"},
|
||||||
"Third": []string{"test", "test2"},
|
"Third": []string{"test", "test2"},
|
||||||
@@ -480,7 +479,7 @@ func Test(t *testing.T, dsf func() models.Datastore) {
|
|||||||
"SECOND": "2",
|
"SECOND": "2",
|
||||||
"THIRD": "3",
|
"THIRD": "3",
|
||||||
},
|
},
|
||||||
Headers: http.Header{
|
Headers: models.Headers{
|
||||||
"First": []string{"test"},
|
"First": []string{"test"},
|
||||||
"Second": []string{"test", "test"},
|
"Second": []string{"test", "test"},
|
||||||
"Third": []string{"test", "test2"},
|
"Third": []string{"test", "test2"},
|
||||||
@@ -500,7 +499,7 @@ func Test(t *testing.T, dsf func() models.Datastore) {
|
|||||||
"SECOND": "",
|
"SECOND": "",
|
||||||
"THIRD": "3",
|
"THIRD": "3",
|
||||||
},
|
},
|
||||||
Headers: http.Header{
|
Headers: models.Headers{
|
||||||
"First": []string{"test2"},
|
"First": []string{"test2"},
|
||||||
"Second": nil,
|
"Second": nil,
|
||||||
},
|
},
|
||||||
@@ -522,7 +521,7 @@ func Test(t *testing.T, dsf func() models.Datastore) {
|
|||||||
"FIRST": "first",
|
"FIRST": "first",
|
||||||
"THIRD": "3",
|
"THIRD": "3",
|
||||||
},
|
},
|
||||||
Headers: http.Header{
|
Headers: models.Headers{
|
||||||
"First": []string{"test2"},
|
"First": []string{"test2"},
|
||||||
"Third": []string{"test", "test2"},
|
"Third": []string{"test", "test2"},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@@ -75,9 +74,6 @@ const (
|
|||||||
|
|
||||||
type sqlStore struct {
|
type sqlStore struct {
|
||||||
db *sqlx.DB
|
db *sqlx.DB
|
||||||
|
|
||||||
// TODO we should prepare all of the statements, rebind them
|
|
||||||
// and store them all here.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New will open the db specified by url, create any tables necessary
|
// New will open the db specified by url, create any tables necessary
|
||||||
@@ -140,17 +136,8 @@ func New(url *url.URL) (models.Datastore, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ds *sqlStore) InsertApp(ctx context.Context, app *models.App) (*models.App, error) {
|
func (ds *sqlStore) InsertApp(ctx context.Context, app *models.App) (*models.App, error) {
|
||||||
var cbyte []byte
|
query := ds.db.Rebind("INSERT INTO apps (name, config) VALUES (:name, :config);")
|
||||||
var err error
|
_, err := ds.db.NamedExecContext(ctx, query, app)
|
||||||
if app.Config != nil {
|
|
||||||
cbyte, err = json.Marshal(app.Config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
query := ds.db.Rebind("INSERT INTO apps (name, config) VALUES (?, ?);")
|
|
||||||
_, err = ds.db.ExecContext(ctx, query, app.Name, string(cbyte))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err := err.(type) {
|
switch err := err.(type) {
|
||||||
case *mysql.MySQLError:
|
case *mysql.MySQLError:
|
||||||
@@ -176,32 +163,19 @@ func (ds *sqlStore) UpdateApp(ctx context.Context, newapp *models.App) (*models.
|
|||||||
app := &models.App{Name: newapp.Name}
|
app := &models.App{Name: newapp.Name}
|
||||||
err := ds.Tx(func(tx *sqlx.Tx) error {
|
err := ds.Tx(func(tx *sqlx.Tx) error {
|
||||||
query := tx.Rebind(`SELECT config FROM apps WHERE name=?`)
|
query := tx.Rebind(`SELECT config FROM apps WHERE name=?`)
|
||||||
row := tx.QueryRowContext(ctx, query, app.Name)
|
row := tx.QueryRowxContext(ctx, query, app.Name)
|
||||||
|
|
||||||
var config string
|
err := row.StructScan(app)
|
||||||
if err := row.Scan(&config); err != nil {
|
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return models.ErrAppsNotFound
|
return models.ErrAppsNotFound
|
||||||
}
|
} else if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if config != "" {
|
|
||||||
err := json.Unmarshal([]byte(config), &app.Config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
app.UpdateConfig(newapp.Config)
|
app.UpdateConfig(newapp.Config)
|
||||||
|
|
||||||
cbyte, err := json.Marshal(app.Config)
|
query = tx.Rebind(`UPDATE apps SET config=:config WHERE name=:name`)
|
||||||
if err != nil {
|
res, err := tx.NamedExecContext(ctx, query, app)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
query = tx.Rebind(`UPDATE apps SET config=? WHERE name=?`)
|
|
||||||
res, err := tx.ExecContext(ctx, query, string(cbyte), app.Name)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -254,29 +228,16 @@ func (ds *sqlStore) RemoveApp(ctx context.Context, appName string) error {
|
|||||||
|
|
||||||
func (ds *sqlStore) GetApp(ctx context.Context, name string) (*models.App, error) {
|
func (ds *sqlStore) GetApp(ctx context.Context, name string) (*models.App, error) {
|
||||||
query := ds.db.Rebind(`SELECT name, config FROM apps WHERE name=?`)
|
query := ds.db.Rebind(`SELECT name, config FROM apps WHERE name=?`)
|
||||||
row := ds.db.QueryRowContext(ctx, query, name)
|
row := ds.db.QueryRowxContext(ctx, query, name)
|
||||||
|
|
||||||
var resName, config string
|
var res models.App
|
||||||
err := row.Scan(&resName, &config)
|
err := row.StructScan(&res)
|
||||||
if err != nil {
|
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return nil, models.ErrAppsNotFound
|
return nil, models.ErrAppsNotFound
|
||||||
}
|
} else if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return &res, nil
|
||||||
res := &models.App{
|
|
||||||
Name: resName,
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(config) > 0 {
|
|
||||||
err := json.Unmarshal([]byte(config), &res.Config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetApps retrieves an array of apps according to a specific filter.
|
// GetApps retrieves an array of apps according to a specific filter.
|
||||||
@@ -284,7 +245,7 @@ func (ds *sqlStore) GetApps(ctx context.Context, filter *models.AppFilter) ([]*m
|
|||||||
res := []*models.App{}
|
res := []*models.App{}
|
||||||
query, args := buildFilterAppQuery(filter)
|
query, args := buildFilterAppQuery(filter)
|
||||||
query = ds.db.Rebind(fmt.Sprintf("SELECT DISTINCT name, config FROM apps %s", query))
|
query = ds.db.Rebind(fmt.Sprintf("SELECT DISTINCT name, config FROM apps %s", query))
|
||||||
rows, err := ds.db.QueryContext(ctx, query, args...)
|
rows, err := ds.db.QueryxContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -292,8 +253,7 @@ func (ds *sqlStore) GetApps(ctx context.Context, filter *models.AppFilter) ([]*m
|
|||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var app models.App
|
var app models.App
|
||||||
err := scanApp(rows, &app)
|
err := rows.StructScan(&app)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return res, nil
|
return res, nil
|
||||||
@@ -310,17 +270,7 @@ func (ds *sqlStore) GetApps(ctx context.Context, filter *models.AppFilter) ([]*m
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ds *sqlStore) InsertRoute(ctx context.Context, route *models.Route) (*models.Route, error) {
|
func (ds *sqlStore) InsertRoute(ctx context.Context, route *models.Route) (*models.Route, error) {
|
||||||
hbyte, err := json.Marshal(route.Headers)
|
err := ds.Tx(func(tx *sqlx.Tx) error {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cbyte, err := json.Marshal(route.Config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ds.Tx(func(tx *sqlx.Tx) error {
|
|
||||||
query := tx.Rebind(`SELECT 1 FROM apps WHERE name=?`)
|
query := tx.Rebind(`SELECT 1 FROM apps WHERE name=?`)
|
||||||
r := tx.QueryRowContext(ctx, query, route.AppName)
|
r := tx.QueryRowContext(ctx, query, route.AppName)
|
||||||
if err := r.Scan(new(int)); err != nil {
|
if err := r.Scan(new(int)); err != nil {
|
||||||
@@ -350,20 +300,20 @@ func (ds *sqlStore) InsertRoute(ctx context.Context, route *models.Route) (*mode
|
|||||||
headers,
|
headers,
|
||||||
config
|
config
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`)
|
VALUES (
|
||||||
|
:app_name,
|
||||||
|
:path,
|
||||||
|
:image,
|
||||||
|
:format,
|
||||||
|
:memory,
|
||||||
|
:type,
|
||||||
|
:timeout,
|
||||||
|
:idle_timeout,
|
||||||
|
:headers,
|
||||||
|
:config
|
||||||
|
);`)
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, query,
|
_, err = tx.NamedExecContext(ctx, query, route)
|
||||||
route.AppName,
|
|
||||||
route.Path,
|
|
||||||
route.Image,
|
|
||||||
route.Format,
|
|
||||||
route.Memory,
|
|
||||||
route.Type,
|
|
||||||
route.Timeout,
|
|
||||||
route.IdleTimeout,
|
|
||||||
string(hbyte),
|
|
||||||
string(cbyte),
|
|
||||||
)
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
@@ -375,8 +325,10 @@ func (ds *sqlStore) UpdateRoute(ctx context.Context, newroute *models.Route) (*m
|
|||||||
var route models.Route
|
var route models.Route
|
||||||
err := ds.Tx(func(tx *sqlx.Tx) error {
|
err := ds.Tx(func(tx *sqlx.Tx) error {
|
||||||
query := tx.Rebind(fmt.Sprintf("%s WHERE app_name=? AND path=?", routeSelector))
|
query := tx.Rebind(fmt.Sprintf("%s WHERE app_name=? AND path=?", routeSelector))
|
||||||
row := tx.QueryRowContext(ctx, query, newroute.AppName, newroute.Path)
|
row := tx.QueryRowxContext(ctx, query, newroute.AppName, newroute.Path)
|
||||||
if err := scanRoute(row, &route); err == sql.ErrNoRows {
|
|
||||||
|
err := row.StructScan(&route)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
return models.ErrRoutesNotFound
|
return models.ErrRoutesNotFound
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -384,40 +336,18 @@ func (ds *sqlStore) UpdateRoute(ctx context.Context, newroute *models.Route) (*m
|
|||||||
|
|
||||||
route.Update(newroute)
|
route.Update(newroute)
|
||||||
|
|
||||||
hbyte, err := json.Marshal(route.Headers)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cbyte, err := json.Marshal(route.Config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
query = tx.Rebind(`UPDATE routes SET
|
query = tx.Rebind(`UPDATE routes SET
|
||||||
image = ?,
|
image = :image,
|
||||||
format = ?,
|
format = :format,
|
||||||
memory = ?,
|
memory = :memory,
|
||||||
type = ?,
|
type = :type,
|
||||||
timeout = ?,
|
timeout = :timeout,
|
||||||
idle_timeout = ?,
|
idle_timeout = :idle_timeout,
|
||||||
headers = ?,
|
headers = :headers,
|
||||||
config = ?
|
config = :config
|
||||||
WHERE app_name=? AND path=?;`)
|
WHERE app_name=:app_name AND path=:path;`)
|
||||||
|
|
||||||
res, err := tx.ExecContext(ctx, query,
|
|
||||||
route.Image,
|
|
||||||
route.Format,
|
|
||||||
route.Memory,
|
|
||||||
route.Type,
|
|
||||||
route.Timeout,
|
|
||||||
route.IdleTimeout,
|
|
||||||
string(hbyte),
|
|
||||||
string(cbyte),
|
|
||||||
route.AppName,
|
|
||||||
route.Path,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
res, err := tx.NamedExecContext(ctx, query, &route)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -460,10 +390,10 @@ func (ds *sqlStore) RemoveRoute(ctx context.Context, appName, routePath string)
|
|||||||
func (ds *sqlStore) GetRoute(ctx context.Context, appName, routePath string) (*models.Route, error) {
|
func (ds *sqlStore) GetRoute(ctx context.Context, appName, routePath string) (*models.Route, error) {
|
||||||
rSelectCondition := "%s WHERE app_name=? AND path=?"
|
rSelectCondition := "%s WHERE app_name=? AND path=?"
|
||||||
query := ds.db.Rebind(fmt.Sprintf(rSelectCondition, routeSelector))
|
query := ds.db.Rebind(fmt.Sprintf(rSelectCondition, routeSelector))
|
||||||
row := ds.db.QueryRowContext(ctx, query, appName, routePath)
|
row := ds.db.QueryRowxContext(ctx, query, appName, routePath)
|
||||||
|
|
||||||
var route models.Route
|
var route models.Route
|
||||||
err := scanRoute(row, &route)
|
err := row.StructScan(&route)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return nil, models.ErrRoutesNotFound
|
return nil, models.ErrRoutesNotFound
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
@@ -484,7 +414,7 @@ func (ds *sqlStore) GetRoutesByApp(ctx context.Context, appName string, filter *
|
|||||||
|
|
||||||
query := fmt.Sprintf("%s %s", routeSelector, filterQuery)
|
query := fmt.Sprintf("%s %s", routeSelector, filterQuery)
|
||||||
query = ds.db.Rebind(query)
|
query = ds.db.Rebind(query)
|
||||||
rows, err := ds.db.QueryContext(ctx, query, args...)
|
rows, err := ds.db.QueryxContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return res, nil // no error for empty list
|
return res, nil // no error for empty list
|
||||||
@@ -495,12 +425,11 @@ func (ds *sqlStore) GetRoutesByApp(ctx context.Context, appName string, filter *
|
|||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var route models.Route
|
var route models.Route
|
||||||
err := scanRoute(rows, &route)
|
err := rows.StructScan(&route)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
res = append(res, &route)
|
res = append(res, &route)
|
||||||
|
|
||||||
}
|
}
|
||||||
if err := rows.Err(); err != nil {
|
if err := rows.Err(); err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
@@ -534,28 +463,27 @@ func (ds *sqlStore) InsertCall(ctx context.Context, call *models.Call) error {
|
|||||||
app_name,
|
app_name,
|
||||||
path
|
path
|
||||||
)
|
)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?);`)
|
VALUES (
|
||||||
|
:id,
|
||||||
|
:created_at,
|
||||||
|
:started_at,
|
||||||
|
:completed_at,
|
||||||
|
:status,
|
||||||
|
:app_name,
|
||||||
|
:path
|
||||||
|
);`)
|
||||||
|
|
||||||
_, err := ds.db.ExecContext(ctx, query, call.ID, call.CreatedAt.String(),
|
_, err := ds.db.NamedExecContext(ctx, query, call)
|
||||||
call.StartedAt.String(), call.CompletedAt.String(),
|
|
||||||
call.Status, call.AppName, call.Path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO calls are not fully qualified in this backend currently. need to discuss,
|
|
||||||
// if we store the whole thing then it adds a lot of disk space and then we can
|
|
||||||
// make async only queue hints instead of entire calls (mq a lot smaller space wise). pick.
|
|
||||||
func (ds *sqlStore) GetCall(ctx context.Context, appName, callID string) (*models.Call, error) {
|
func (ds *sqlStore) GetCall(ctx context.Context, appName, callID string) (*models.Call, error) {
|
||||||
query := fmt.Sprintf(`%s WHERE id=? AND app_name=?`, callSelector)
|
query := fmt.Sprintf(`%s WHERE id=? AND app_name=?`, callSelector)
|
||||||
query = ds.db.Rebind(query)
|
query = ds.db.Rebind(query)
|
||||||
row := ds.db.QueryRowContext(ctx, query, callID, appName)
|
row := ds.db.QueryRowxContext(ctx, query, callID, appName)
|
||||||
|
|
||||||
var call models.Call
|
var call models.Call
|
||||||
err := scanCall(row, &call)
|
err := row.StructScan(&call)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -567,7 +495,7 @@ func (ds *sqlStore) GetCalls(ctx context.Context, filter *models.CallFilter) ([]
|
|||||||
query, args := buildFilterCallQuery(filter)
|
query, args := buildFilterCallQuery(filter)
|
||||||
query = fmt.Sprintf("%s %s", callSelector, query)
|
query = fmt.Sprintf("%s %s", callSelector, query)
|
||||||
query = ds.db.Rebind(query)
|
query = ds.db.Rebind(query)
|
||||||
rows, err := ds.db.QueryContext(ctx, query, args...)
|
rows, err := ds.db.QueryxContext(ctx, query, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -575,7 +503,7 @@ func (ds *sqlStore) GetCalls(ctx context.Context, filter *models.CallFilter) ([]
|
|||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var call models.Call
|
var call models.Call
|
||||||
err := scanCall(rows, &call)
|
err := rows.StructScan(&call)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -631,75 +559,6 @@ func (ds *sqlStore) DeleteLog(ctx context.Context, appName, callID string) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO scrap for sqlx scanx ?? some things aren't perfect (e.g. config is a json string)
|
|
||||||
type RowScanner interface {
|
|
||||||
Scan(dest ...interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func ScanLog(scanner RowScanner, log *models.CallLog) error {
|
|
||||||
return scanner.Scan(
|
|
||||||
&log.CallID,
|
|
||||||
&log.Log,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func scanRoute(scanner RowScanner, route *models.Route) error {
|
|
||||||
var headerStr string
|
|
||||||
var configStr string
|
|
||||||
|
|
||||||
err := scanner.Scan(
|
|
||||||
&route.AppName,
|
|
||||||
&route.Path,
|
|
||||||
&route.Image,
|
|
||||||
&route.Format,
|
|
||||||
&route.Memory,
|
|
||||||
&route.Type,
|
|
||||||
&route.Timeout,
|
|
||||||
&route.IdleTimeout,
|
|
||||||
&headerStr,
|
|
||||||
&configStr,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(headerStr) > 0 {
|
|
||||||
err = json.Unmarshal([]byte(headerStr), &route.Headers)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(configStr) > 0 {
|
|
||||||
err = json.Unmarshal([]byte(configStr), &route.Config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func scanApp(scanner RowScanner, app *models.App) error {
|
|
||||||
var configStr string
|
|
||||||
|
|
||||||
err := scanner.Scan(
|
|
||||||
&app.Name,
|
|
||||||
&configStr,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(configStr) > 0 {
|
|
||||||
err = json.Unmarshal([]byte(configStr), &app.Config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildFilterRouteQuery(filter *models.RouteFilter) (string, []interface{}) {
|
func buildFilterRouteQuery(filter *models.RouteFilter) (string, []interface{}) {
|
||||||
if filter == nil {
|
if filter == nil {
|
||||||
return "", nil
|
return "", nil
|
||||||
@@ -794,25 +653,6 @@ func buildFilterCallQuery(filter *models.CallFilter) (string, []interface{}) {
|
|||||||
return b.String(), args
|
return b.String(), args
|
||||||
}
|
}
|
||||||
|
|
||||||
func scanCall(scanner RowScanner, call *models.Call) error {
|
|
||||||
err := scanner.Scan(
|
|
||||||
&call.ID,
|
|
||||||
&call.CreatedAt,
|
|
||||||
&call.StartedAt,
|
|
||||||
&call.CompletedAt,
|
|
||||||
&call.Status,
|
|
||||||
&call.AppName,
|
|
||||||
&call.Path,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return models.ErrCallNotFound
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetDatabase returns the underlying sqlx database implementation
|
// GetDatabase returns the underlying sqlx database implementation
|
||||||
func (ds *sqlStore) GetDatabase() *sqlx.DB {
|
func (ds *sqlStore) GetDatabase() *sqlx.DB {
|
||||||
return ds.db
|
return ds.db
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name" db:"name"`
|
||||||
Routes Routes `json:"routes,omitempty"`
|
Config Config `json:"config" db:"config"`
|
||||||
Config `json:"config"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) Validate() error {
|
func (a *App) Validate() error {
|
||||||
@@ -24,11 +23,6 @@ func (a *App) Validate() error {
|
|||||||
func (a *App) Clone() *App {
|
func (a *App) Clone() *App {
|
||||||
var c App
|
var c App
|
||||||
c.Name = a.Name
|
c.Name = a.Name
|
||||||
if a.Routes != nil {
|
|
||||||
for i := range a.Routes {
|
|
||||||
c.Routes = append(c.Routes, a.Routes[i].Clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if a.Config != nil {
|
if a.Config != nil {
|
||||||
c.Config = make(Config)
|
c.Config = make(Config)
|
||||||
for k, v := range a.Config {
|
for k, v := range a.Config {
|
||||||
|
|||||||
@@ -23,15 +23,15 @@ const (
|
|||||||
var possibleStatuses = [...]string{"delayed", "queued", "running", "success", "error", "cancelled"}
|
var possibleStatuses = [...]string{"delayed", "queued", "running", "success", "error", "cancelled"}
|
||||||
|
|
||||||
type CallLog struct {
|
type CallLog struct {
|
||||||
CallID string `json:"call_id"`
|
CallID string `json:"call_id" db:"id"`
|
||||||
Log string `json:"log"`
|
Log string `json:"log" db:"log"`
|
||||||
AppName string `json:"app_name"`
|
AppName string `json:"app_name" db:"app_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call is a representation of a specific invocation of a route.
|
// Call is a representation of a specific invocation of a route.
|
||||||
type Call struct {
|
type Call struct {
|
||||||
// Unique identifier representing a specific call.
|
// Unique identifier representing a specific call.
|
||||||
ID string `json:"id"`
|
ID string `json:"id" db:"id"`
|
||||||
|
|
||||||
// NOTE: this is stale, retries are not implemented atm, but this is nice, so leaving
|
// NOTE: this is stale, retries are not implemented atm, but this is nice, so leaving
|
||||||
// States and valid transitions.
|
// States and valid transitions.
|
||||||
@@ -68,65 +68,65 @@ type Call struct {
|
|||||||
// - bad_exit - exited with non-zero status due to program termination/crash.
|
// - bad_exit - exited with non-zero status due to program termination/crash.
|
||||||
// * cancelled - cancelled via API. More information in the reason field.
|
// * cancelled - cancelled via API. More information in the reason field.
|
||||||
// - client_request - Request was cancelled by a client.
|
// - client_request - Request was cancelled by a client.
|
||||||
Status string `json:"status"`
|
Status string `json:"status" db:"status"`
|
||||||
|
|
||||||
// App this call belongs to.
|
// App this call belongs to.
|
||||||
AppName string `json:"app_name"`
|
AppName string `json:"app_name" db:"app_name"`
|
||||||
|
|
||||||
// Path of the route that is responsible for this call
|
// Path of the route that is responsible for this call
|
||||||
Path string `json:"path"`
|
Path string `json:"path" db:"path"`
|
||||||
|
|
||||||
// Name of Docker image to use.
|
// Name of Docker image to use.
|
||||||
Image string `json:"image"`
|
Image string `json:"image,omitempty" db:"-"`
|
||||||
|
|
||||||
// Number of seconds to wait before queueing the call for consumption for the
|
// Number of seconds to wait before queueing the call for consumption for the
|
||||||
// first time. Must be a positive integer. Calls with a delay start in state
|
// first time. Must be a positive integer. Calls with a delay start in state
|
||||||
// "delayed" and transition to "running" after delay seconds.
|
// "delayed" and transition to "running" after delay seconds.
|
||||||
Delay int32 `json:"delay,omitempty"`
|
Delay int32 `json:"delay,omitempty" db:"-"`
|
||||||
|
|
||||||
// Type indicates whether a task is to be run synchronously or asynchronously.
|
// Type indicates whether a task is to be run synchronously or asynchronously.
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty" db:"-"`
|
||||||
|
|
||||||
// Format is the format to pass input into the function.
|
// Format is the format to pass input into the function.
|
||||||
Format string `json:"format,omitempty"`
|
Format string `json:"format,omitempty" db:"-"`
|
||||||
|
|
||||||
// Payload for the call. This is only used by async calls, to store their input.
|
// Payload for the call. This is only used by async calls, to store their input.
|
||||||
// TODO should we copy it into here too for debugging sync?
|
// TODO should we copy it into here too for debugging sync?
|
||||||
Payload string `json:"payload,omitempty"`
|
Payload string `json:"payload,omitempty" db:"-"`
|
||||||
|
|
||||||
// Full request url that spawned this invocation.
|
// Full request url that spawned this invocation.
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty" db:"-"`
|
||||||
|
|
||||||
// Method of the http request used to make this call.
|
// Method of the http request used to make this call.
|
||||||
Method string `json:"method,omitempty"`
|
Method string `json:"method,omitempty" db:"-"`
|
||||||
|
|
||||||
// Priority of the call. Higher has more priority. 3 levels from 0-2. Calls
|
// Priority of the call. Higher has more priority. 3 levels from 0-2. Calls
|
||||||
// at same priority are processed in FIFO order.
|
// at same priority are processed in FIFO order.
|
||||||
Priority *int32 `json:"priority"`
|
Priority *int32 `json:"priority,omitempty" db:"-"`
|
||||||
|
|
||||||
// Maximum runtime in seconds.
|
// Maximum runtime in seconds.
|
||||||
Timeout int32 `json:"timeout,omitempty"`
|
Timeout int32 `json:"timeout,omitempty" db:"-"`
|
||||||
|
|
||||||
// Hot function idle timeout in seconds before termination.
|
// Hot function idle timeout in seconds before termination.
|
||||||
IdleTimeout int32 `json:"idle_timeout,omitempty"`
|
IdleTimeout int32 `json:"idle_timeout,omitempty" db:"-"`
|
||||||
|
|
||||||
// Memory is the amount of RAM this call is allocated.
|
// Memory is the amount of RAM this call is allocated.
|
||||||
Memory uint64 `json:"memory,omitempty"`
|
Memory uint64 `json:"memory,omitempty" db:"-"`
|
||||||
|
|
||||||
// BaseEnv are the env vars for hot containers, not request specific.
|
// BaseEnv are the env vars for hot containers, not request specific.
|
||||||
BaseEnv map[string]string `json:"base_env,omitempty"`
|
BaseEnv map[string]string `json:"base_env,omitempty" db:"-"`
|
||||||
|
|
||||||
// Env vars for the call. Comes from the ones set on the Route.
|
// Env vars for the call. Comes from the ones set on the Route.
|
||||||
EnvVars map[string]string `json:"env_vars,omitempty"`
|
EnvVars map[string]string `json:"env_vars,omitempty" db:"-"`
|
||||||
|
|
||||||
// Time when call completed, whether it was successul or failed. Always in UTC.
|
// Time when call completed, whether it was successul or failed. Always in UTC.
|
||||||
CompletedAt strfmt.DateTime `json:"completed_at,omitempty"`
|
CompletedAt strfmt.DateTime `json:"completed_at,omitempty" db:"completed_at"`
|
||||||
|
|
||||||
// Time when call was submitted. Always in UTC.
|
// Time when call was submitted. Always in UTC.
|
||||||
CreatedAt strfmt.DateTime `json:"created_at,omitempty"`
|
CreatedAt strfmt.DateTime `json:"created_at,omitempty" db:"created_at"`
|
||||||
|
|
||||||
// Time when call started execution. Always in UTC.
|
// Time when call started execution. Always in UTC.
|
||||||
StartedAt strfmt.DateTime `json:"started_at,omitempty"`
|
StartedAt strfmt.DateTime `json:"started_at,omitempty" db:"started_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CallFilter struct {
|
type CallFilter struct {
|
||||||
|
|||||||
@@ -1,7 +1,96 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
type Config map[string]string
|
type Config map[string]string
|
||||||
|
|
||||||
func (c *Config) Validate() error {
|
func (c *Config) Validate() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// implements sql.Valuer, returning a string
|
||||||
|
func (c Config) Value() (driver.Value, error) {
|
||||||
|
if len(c) < 1 {
|
||||||
|
return driver.Value(string("")), nil
|
||||||
|
}
|
||||||
|
var b bytes.Buffer
|
||||||
|
err := json.NewEncoder(&b).Encode(c)
|
||||||
|
// return a string type
|
||||||
|
return driver.Value(b.String()), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// implements sql.Scanner
|
||||||
|
func (c *Config) Scan(value interface{}) error {
|
||||||
|
if value == nil {
|
||||||
|
*c = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
bv, err := driver.String.ConvertValue(value)
|
||||||
|
if err == nil {
|
||||||
|
var b []byte
|
||||||
|
switch x := bv.(type) {
|
||||||
|
case []byte:
|
||||||
|
b = x
|
||||||
|
case string:
|
||||||
|
b = []byte(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) > 0 {
|
||||||
|
return json.Unmarshal(b, c)
|
||||||
|
} else {
|
||||||
|
*c = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, return an error
|
||||||
|
return fmt.Errorf("config invalid db format: %T %T value, err: %v", value, bv, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Headers is an http.Header that implements additional methods.
|
||||||
|
type Headers http.Header
|
||||||
|
|
||||||
|
// implements sql.Valuer, returning a string
|
||||||
|
func (h Headers) Value() (driver.Value, error) {
|
||||||
|
if len(h) < 1 {
|
||||||
|
return driver.Value(string("")), nil
|
||||||
|
}
|
||||||
|
var b bytes.Buffer
|
||||||
|
err := json.NewEncoder(&b).Encode(h)
|
||||||
|
// return a string type
|
||||||
|
return driver.Value(b.String()), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// implements sql.Scanner
|
||||||
|
func (h *Headers) Scan(value interface{}) error {
|
||||||
|
if value == nil {
|
||||||
|
*h = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
bv, err := driver.String.ConvertValue(value)
|
||||||
|
if err == nil {
|
||||||
|
var b []byte
|
||||||
|
switch x := bv.(type) {
|
||||||
|
case []byte:
|
||||||
|
b = x
|
||||||
|
case string:
|
||||||
|
b = []byte(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b) > 0 {
|
||||||
|
return json.Unmarshal(b, h)
|
||||||
|
} else {
|
||||||
|
*h = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, return an error
|
||||||
|
return fmt.Errorf("headers invalid db format: %T %T value, err: %v", value, bv, err)
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,16 +15,16 @@ const (
|
|||||||
type Routes []*Route
|
type Routes []*Route
|
||||||
|
|
||||||
type Route struct {
|
type Route struct {
|
||||||
AppName string `json:"app_name"`
|
AppName string `json:"app_name" db:"app_name"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path" db:"path"`
|
||||||
Image string `json:"image"`
|
Image string `json:"image" db:"image"`
|
||||||
Memory uint64 `json:"memory"`
|
Memory uint64 `json:"memory" db:"memory"`
|
||||||
Headers http.Header `json:"headers"`
|
Headers Headers `json:"headers" db:"headers"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type" db:"type"`
|
||||||
Format string `json:"format"`
|
Format string `json:"format" db":format"`
|
||||||
Timeout int32 `json:"timeout"`
|
Timeout int32 `json:"timeout" db:"timeout"`
|
||||||
IdleTimeout int32 `json:"idle_timeout"`
|
IdleTimeout int32 `json:"idle_timeout" db:"idle_timeout"`
|
||||||
Config `json:"config"`
|
Config Config `json:"config" db:"config"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDefaults sets zeroed field to defaults.
|
// SetDefaults sets zeroed field to defaults.
|
||||||
@@ -42,7 +42,7 @@ func (r *Route) SetDefaults() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if r.Headers == nil {
|
if r.Headers == nil {
|
||||||
r.Headers = http.Header{}
|
r.Headers = Headers(http.Header{})
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Config == nil {
|
if r.Config == nil {
|
||||||
@@ -144,11 +144,11 @@ func (r *Route) Update(new *Route) {
|
|||||||
}
|
}
|
||||||
if new.Headers != nil {
|
if new.Headers != nil {
|
||||||
if r.Headers == nil {
|
if r.Headers == nil {
|
||||||
r.Headers = make(http.Header)
|
r.Headers = Headers(make(http.Header))
|
||||||
}
|
}
|
||||||
for k, v := range new.Headers {
|
for k, v := range new.Headers {
|
||||||
if len(v) == 0 {
|
if len(v) == 0 {
|
||||||
r.Headers.Del(k)
|
http.Header(r.Headers).Del(k)
|
||||||
} else {
|
} else {
|
||||||
r.Headers[k] = v
|
r.Headers[k] = v
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ func TestRouteDelete(t *testing.T) {
|
|||||||
buf := setLogBuffer()
|
buf := setLogBuffer()
|
||||||
|
|
||||||
routes := []*models.Route{{AppName: "a", Path: "/myroute"}}
|
routes := []*models.Route{{AppName: "a", Path: "/myroute"}}
|
||||||
apps := []*models.App{{Name: "a", Routes: routes, Config: nil}}
|
apps := []*models.App{{Name: "a", Config: nil}}
|
||||||
|
|
||||||
for i, test := range []struct {
|
for i, test := range []struct {
|
||||||
ds models.Datastore
|
ds models.Datastore
|
||||||
|
|||||||
Reference in New Issue
Block a user