diff --git a/app/servers.go b/app/servers.go index c482029a7..b0d186912 100644 --- a/app/servers.go +++ b/app/servers.go @@ -108,8 +108,8 @@ func (app *CortezaApp) mountHttpRoutes(r chi.Router) { // temp api gateway support { - apigw.Setup(struct{}{}, app.Log, service.DefaultStore) - r.Route("/gateway", apigw.Service().Router(context.Background())) + apigw.Setup(options.Apigw(), service.DefaultLogger, service.DefaultStore) + r.Route("/gateway", apigw.Service().Router) } var fullpathDocs = options.CleanBase(ho.BaseUrl, ho.ApiBaseUrl, "docs") diff --git a/def/system.apigw-function.yaml b/def/system.apigw-function.yaml new file mode 100644 index 000000000..f59b8cebb --- /dev/null +++ b/def/system.apigw-function.yaml @@ -0,0 +1,8 @@ +rbac: + operations: + read: + description: Read API Gateway function + update: + description: Update API Gateway function + delete: + description: Delete API Gateway function diff --git a/def/system.route.yaml b/def/system.apigw-route.yaml similarity index 100% rename from def/system.route.yaml rename to def/system.apigw-route.yaml diff --git a/def/system.yaml b/def/system.yaml index 9222d17cf..5f44193c7 100644 --- a/def/system.yaml +++ b/def/system.yaml @@ -52,3 +52,10 @@ rbac: api-gw-route.create: description: Create API gateway route + api-gw-routes.search: + description: List search or filter API gateway routes + + api-gw-function.create: + description: Add API gateway function to route + api-gw-functions.search: + description: List, search or filter functions diff --git a/pkg/apigw/apigw.go b/pkg/apigw/apigw.go index d4095162e..5f5314946 100644 --- a/pkg/apigw/apigw.go +++ b/pkg/apigw/apigw.go @@ -2,7 +2,11 @@ package apigw import ( "context" + "encoding/json" + as "github.com/cortezaproject/corteza-server/automation/service" + "github.com/cortezaproject/corteza-server/pkg/filter" + "github.com/cortezaproject/corteza-server/pkg/options" "github.com/cortezaproject/corteza-server/system/types" "github.com/go-chi/chi" "go.uber.org/zap" @@ -10,11 +14,12 @@ import ( type ( storer interface { - SearchApigwRoutes(ctx context.Context, f types.RouteFilter) (types.RouteSet, types.RouteFilter, error) - SearchApigwFunctions(ctx context.Context, f types.FunctionFilter) (types.FunctionSet, types.FunctionFilter, error) + SearchApigwRoutes(ctx context.Context, f types.ApigwRouteFilter) (types.ApigwRouteSet, types.ApigwRouteFilter, error) + SearchApigwFunctions(ctx context.Context, f types.ApigwFunctionFilter) (types.ApigwFunctionSet, types.ApigwFunctionFilter, error) } apigw struct { + opts *options.ApigwOpt log *zap.Logger reg *registry routes []*route @@ -37,7 +42,7 @@ func Set(a *apigw) { } // Setup handles the singleton service -func Setup(opts interface{}, log *zap.Logger, storer storer) { +func Setup(opts *options.ApigwOpt, log *zap.Logger, storer storer) { if apiGw != nil { return } @@ -45,11 +50,12 @@ func Setup(opts interface{}, log *zap.Logger, storer storer) { apiGw = New(opts, log, storer) } -func New(opts interface{}, logger *zap.Logger, storer storer) *apigw { +func New(opts *options.ApigwOpt, logger *zap.Logger, storer storer) *apigw { reg := NewRegistry() reg.Preload() return &apigw{ + opts: opts, log: logger, storer: storer, reload: make(chan bool), @@ -64,7 +70,7 @@ func (s *apigw) Reload(ctx context.Context) { } func (s *apigw) loadRoutes(ctx context.Context) (rr []*route, err error) { - routes, _, err := s.storer.SearchApigwRoutes(ctx, types.RouteFilter{Enabled: true}) + routes, _, err := s.storer.SearchApigwRoutes(ctx, types.ApigwRouteFilter{Enabled: true, Deleted: filter.StateExcluded}) if err != nil { return @@ -83,66 +89,76 @@ func (s *apigw) loadRoutes(ctx context.Context) (rr []*route, err error) { return } -func (s *apigw) loadFunctions(ctx context.Context, route uint64) (ff []*types.Function, err error) { - ff, _, err = s.storer.SearchApigwFunctions(ctx, types.FunctionFilter{}) +func (s *apigw) loadFunctions(ctx context.Context, route uint64) (ff []*types.ApigwFunction, err error) { + ff, _, err = s.storer.SearchApigwFunctions(ctx, types.ApigwFunctionFilter{RouteID: route}) return } -func (s *apigw) Router(ctx context.Context) func(r chi.Router) { - return func(r chi.Router) { - routes, err := s.loadRoutes(ctx) +func (s *apigw) Router(r chi.Router) { + var ( + ctx = context.Background() + ) - if err != nil { - s.log.Error("could not load routes", zap.Error(err)) - return - } + r.HandleFunc("/", helperDefaultResponse(s.opts)) - s.Init(ctx, routes...) + routes, err := s.loadRoutes(ctx) - for _, route := range s.routes { - r.Handle(route.endpoint, route) - } + if err != nil { + s.log.Error("could not load routes", zap.Error(err)) + return + } - go func() { - for { - select { - case <-s.reload: - s.log.Debug("got reload signal") + s.Init(ctx, routes...) - routes, err := s.loadRoutes(ctx) + for _, route := range s.routes { + r.Handle(route.endpoint, route) + } - if err != nil { - s.log.Error("could not reload routes", zap.Error(err)) - return - } + go func() { + for { + select { + case <-s.reload: + routes, err := s.loadRoutes(ctx) - s.Init(ctx, routes...) - - for _, route := range s.routes { - r.Handle(route.endpoint, route) - } - - case <-ctx.Done(): - s.log.Debug("done! getting out") + if err != nil { + s.log.Error("could not reload API Gateway routes", zap.Error(err)) return } + + s.log.Debug("reloading API Gateway routes and functions", zap.Int("count", len(routes))) + + s.Init(ctx, routes...) + + for _, route := range s.routes { + r.Handle(route.endpoint, route) + } + + case <-ctx.Done(): + s.log.Debug("shutting down API Gateway service") + return } - }() - } + } + }() } // init all the routes func (s *apigw) Init(ctx context.Context, route ...*route) { s.routes = route - s.log.Debug("initializing routes\n", zap.Int("num", len(s.routes))) + s.log.Debug("registering routes", zap.Int("count", len(s.routes))) for _, r := range s.routes { - r.pipe = &pl{} + log := s.log.With(zap.String("route", r.String())) + + r.pipe = NewPipeline(log) + + r.opts = s.opts + r.log = log + regFuncs, err := s.loadFunctions(ctx, r.ID) if err != nil { - s.log.Error("could not load functions for route", zap.String("route", r.endpoint), zap.Error(err)) + log.Error("could not load functions for route", zap.Error(err)) continue } @@ -154,20 +170,31 @@ func (s *apigw) Init(ctx context.Context, route ...*route) { }) for _, f := range regFuncs { - fc := functionHandler{} - h, err := s.reg.Get(f.Ref) if err != nil { - s.log.Error("could not register function for route", zap.String("route", r.endpoint), zap.Error(err)) + log.Error("could not register function for route", zap.Error(err)) continue } - fc.Merge(ctx, h.Meta(f)) - fc.SetHandler(h.Handler()) + enc, err := json.Marshal(f.Params) - r.pipe.Add(fc, f.Params) + if err != nil { + log.Error("could not load params for function", zap.String("ref", f.Ref), zap.Error(err)) + continue + } + + h, err = s.reg.Merge(h, enc) + + if err != nil { + log.Error("could not merge params to handler", zap.String("ref", f.Ref), zap.Error(err)) + continue + } + + r.pipe.Add(h) } + + log.Debug("successfuly registered route") } } @@ -176,10 +203,13 @@ func (s *apigw) Funcs(kind string) (list functionMetaList) { if kind != "" { list, _ = list.Filter(func(fm *functionMeta) (bool, error) { - // return fm. - return fm.Kind == kind, nil + return string(fm.Kind) == kind, nil }) } return } + +func NewWorkflow() (wf WfExecer) { + return as.Workflow(&zap.Logger{}, options.CorredorOpt{}) +} diff --git a/pkg/apigw/apigw_test.go b/pkg/apigw/apigw_test.go index debc459bf..059d99eda 100644 --- a/pkg/apigw/apigw_test.go +++ b/pkg/apigw/apigw_test.go @@ -2,39 +2,187 @@ package apigw import ( "context" - "net/http" - "net/http/httptest" + "errors" "testing" - "github.com/cortezaproject/corteza-server/pkg/expr" - "github.com/cortezaproject/corteza-server/pkg/logger" - "github.com/cortezaproject/corteza-server/pkg/wfexec" + "github.com/cortezaproject/corteza-server/system/types" "github.com/stretchr/testify/require" + "go.uber.org/zap" ) -func execFn(t *testing.T, r *http.Request, fn wfHandler) error { +type ( + // overriding mockHandler with only + // the merge function + mockExistingHandler struct { + *mockHandler + merge func(params []byte) (Handler, error) + } +) + +func Test_serviceLoadRoutes(t *testing.T) { var ( - req = require.New(t) - ctx = context.Background() - scope = &expr.Vars{} - graph = wfexec.NewGraph() - recorder = httptest.NewRecorder() + ctx = context.Background() + req = require.New(t) ) - scope.Set("envelope", envelope{ - Request: r, - Writer: recorder, - }) + mockStorer := &mockStorer{ + r: func(c context.Context, arf types.ApigwRouteFilter) (s types.ApigwRouteSet, f types.ApigwRouteFilter, err error) { + s = types.ApigwRouteSet{ + {ID: 1, Endpoint: "/endpoint", Method: "GET", Debug: false, Enabled: true, Group: 0}, + {ID: 2, Endpoint: "/endpoint2", Method: "POST", Debug: false, Enabled: true, Group: 0}, + } + return + }, + } - step := wfexec.NewGenericStep(fn.self()) + service := &apigw{ + storer: mockStorer, + } - graph.AddStep(step) - - sess := wfexec.NewSession(ctx, graph, wfexec.SetLogger(logger.Default())) - - err := sess.Exec(ctx, step, scope) + r, err := service.loadRoutes(ctx) req.NoError(err) - - return sess.Wait(ctx) + req.Len(r, 2) +} + +func Test_serviceLoadFunctions(t *testing.T) { + var ( + ctx = context.Background() + req = require.New(t) + ) + + mockStorer := &mockStorer{ + f: func(c context.Context, aff types.ApigwFunctionFilter) (s types.ApigwFunctionSet, f types.ApigwFunctionFilter, err error) { + s = types.ApigwFunctionSet{ + {ID: 1, Route: 1}, + {ID: 2, Route: 2}, + } + return + }, + } + + service := &apigw{ + + storer: mockStorer, + } + + r, err := service.loadFunctions(ctx, 2) + + req.NoError(err) + req.Len(r, 2) +} + +func Test_serviceInit(t *testing.T) { + type ( + tf struct { + name string + expLen int + st mockStorer + reg *registry + } + ) + + var ( + tcc = []tf{ + { + name: "could not register 1 function for route", + st: mockStorer{ + r: func(c context.Context, arf types.ApigwRouteFilter) (s types.ApigwRouteSet, f types.ApigwRouteFilter, err error) { + s = types.ApigwRouteSet{ + {ID: 1, Endpoint: "/endpoint", Method: "GET", Debug: false, Enabled: true, Group: 0}, + } + return + }, + f: func(c context.Context, aff types.ApigwFunctionFilter) (s types.ApigwFunctionSet, f types.ApigwFunctionFilter, err error) { + s = types.ApigwFunctionSet{ + {ID: 1, Route: 1, Ref: "testExistingFunction"}, + {ID: 2, Route: 1, Ref: "testNotExistingFunction"}, + } + return + }, + }, + reg: ®istry{ + h: map[string]Handler{"testExistingFunction": &mockHandler{}}, + }, + expLen: 1, + }, + { + name: "successful register of 2 functions for route", + st: mockStorer{ + r: func(c context.Context, arf types.ApigwRouteFilter) (s types.ApigwRouteSet, f types.ApigwRouteFilter, err error) { + s = types.ApigwRouteSet{ + {ID: 1, Endpoint: "/endpoint", Method: "GET", Debug: false, Enabled: true, Group: 0}, + } + return + }, + f: func(c context.Context, aff types.ApigwFunctionFilter) (s types.ApigwFunctionSet, f types.ApigwFunctionFilter, err error) { + s = types.ApigwFunctionSet{ + {ID: 1, Route: 1, Ref: "testExistingFunction"}, + {ID: 2, Route: 1, Ref: "testExistingFunction"}, + } + return + }, + }, + reg: ®istry{ + h: map[string]Handler{"testExistingFunction": &mockHandler{}}, + }, + expLen: 2, + }, + { + name: "could not merge params for function", + st: mockStorer{ + r: func(c context.Context, arf types.ApigwRouteFilter) (s types.ApigwRouteSet, f types.ApigwRouteFilter, err error) { + s = types.ApigwRouteSet{ + {ID: 1, Endpoint: "/endpoint", Method: "GET", Debug: false, Enabled: true, Group: 0}, + } + return + }, + f: func(c context.Context, aff types.ApigwFunctionFilter) (s types.ApigwFunctionSet, f types.ApigwFunctionFilter, err error) { + s = types.ApigwFunctionSet{ + {ID: 1, Route: 1, Ref: "testExistingFunction", Params: types.ApigwFuncParams{}}, + } + return + }, + }, + reg: ®istry{ + h: map[string]Handler{ + "testExistingFunction": &mockExistingHandler{ + merge: func(params []byte) (Handler, error) { + return nil, errors.New("testttt") + }, + }, + }, + }, + expLen: 0, + }, + } + ) + + for _, tc := range tcc { + t.Run(tc.name, func(t *testing.T) { + var ( + req = require.New(t) + ctx = context.Background() + ) + + service := &apigw{ + log: zap.NewNop(), + storer: tc.st, + reg: tc.reg, + } + + rr, err := service.loadRoutes(ctx) + req.NoError(err) + + service.Init(ctx, rr...) + + req.NotEmpty(service.routes) + req.Len(service.routes[0].pipe.w, tc.expLen) + }) + } + +} + +func (h mockExistingHandler) Merge(params []byte) (Handler, error) { + return h.merge(params) } diff --git a/pkg/apigw/auth.go b/pkg/apigw/auth.go new file mode 100644 index 000000000..e776ba68a --- /dev/null +++ b/pkg/apigw/auth.go @@ -0,0 +1,128 @@ +package apigw + +import ( + "encoding/base64" + "fmt" + "net/http" +) + +const ( + authTypeOauth2 authType = "oauth2" + authTypeHeader authType = "header" + authTypeQuery authType = "query" + authTypeBasic authType = "basic" + authTypeJwt authType = "jwt" + authTypeNoop authType = "noop" +) + +type ( + AuthServicer interface { + Do(*http.Request) error + } + + authServicerNoop struct{} + + authServicerHeader struct { + params map[string]interface{} + } + + authServicerQuery struct { + params map[string]interface{} + } + + authServicerBasic struct { + user string + pass string + } + + authType string + + authParams struct { + Type authType `json:"type"` + Params map[string]interface{} `json:"params"` + } +) + +func NewAuthHeader(p authParams) (s authServicerHeader, err error) { + s = authServicerHeader{ + params: p.Params, + } + + return +} + +func NewAuthQuery(p authParams) (s authServicerQuery, err error) { + s = authServicerQuery{ + params: p.Params, + } + + return +} + +func NewAuthBasic(p authParams) (s authServicerBasic, err error) { + var ( + ok bool + user, pass string + ) + + if user, ok = p.Params["username"].(string); !ok { + err = fmt.Errorf("invalid param username") + return + } + + if pass, ok = p.Params["password"].(string); !ok { + err = fmt.Errorf("invalid param password") + return + } + + s = authServicerBasic{user: user, pass: pass} + + return +} + +func NewAuthServicer(c *http.Client, p authParams) (AuthServicer, error) { + switch p.Type { + case authTypeHeader: + return NewAuthHeader(p) + case authTypeQuery: + return NewAuthQuery(p) + case authTypeBasic: + return NewAuthBasic(p) + default: + return authServicerNoop{}, nil + } +} + +func (s authServicerHeader) Do(r *http.Request) error { + for k, v := range s.params { + r.Header.Add(k, v.(string)) + } + return nil +} + +func (s authServicerQuery) Do(r *http.Request) error { + if len(s.params) == 0 { + return nil + } + + q := r.URL.Query() + + for k, v := range s.params { + q.Set(k, v.(string)) + } + + r.URL.RawQuery = q.Encode() + + return nil +} + +func (s authServicerBasic) Do(r *http.Request) error { + bs := base64.URLEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", s.user, s.pass))) + + r.Header.Set("Authorization", fmt.Sprintf("Basic %s", bs)) + return nil +} + +func (s authServicerNoop) Do(r *http.Request) error { + return nil +} diff --git a/pkg/apigw/auth_test.go b/pkg/apigw/auth_test.go new file mode 100644 index 000000000..2c6313e7f --- /dev/null +++ b/pkg/apigw/auth_test.go @@ -0,0 +1,121 @@ +package apigw + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/require" +) + +type ( + mockRoundTripper func(*http.Request) (*http.Response, error) +) + +func (mrt mockRoundTripper) RoundTrip(rq *http.Request) (r *http.Response, err error) { + return mrt(rq) +} + +func Test_authDo(t *testing.T) { + type ( + tf struct { + name string + err string + errv string + params authParams + exp http.Header + } + ) + + var ( + tcc = []tf{ + { + name: "auth header match headers", + params: authParams{ + Type: authTypeHeader, + Params: map[string]interface{}{ + "Client-Id": "123455", + "Client_credentials": "pass1234", + }, + }, + exp: http.Header{ + "Client-Id": []string{"123455"}, + "Client_credentials": []string{"pass1234"}, + }, + }, + { + name: "auth header match canonicalized headers", + params: authParams{ + Type: authTypeHeader, + Params: map[string]interface{}{ + "camelCaseHeader": "123455", + }, + }, + exp: http.Header{ + "Camelcaseheader": []string{"123455"}, + }, + }, + { + name: "auth basic match headers", + params: authParams{ + Type: authTypeBasic, + Params: map[string]interface{}{ + "username": "user", + "password": "pass1234", + }, + }, + exp: http.Header{"Authorization": []string{"Basic dXNlcjpwYXNzMTIzNA=="}}, + }, + { + name: "auth basic match headers fail user validation", + params: authParams{ + Type: authTypeBasic, + Params: map[string]interface{}{"password": "pass1234"}, + }, + exp: http.Header{}, + errv: "invalid param username", + }, + { + name: "auth basic match headers fail pass validation", + params: authParams{ + Type: authTypeBasic, + Params: map[string]interface{}{"username": "user"}, + }, + exp: http.Header{}, + errv: "invalid param password", + }, + { + name: "noop default fallback", + params: authParams{}, + exp: http.Header{}, + }, + } + ) + + for _, tc := range tcc { + t.Run(tc.name, func(t *testing.T) { + var ( + req = require.New(t) + c = http.DefaultClient + ) + + c.Transport = mockRoundTripper(func(r *http.Request) (rs *http.Response, err error) { return }) + + rq, _ := http.NewRequest("POST", "/foo", http.NoBody) + + authServicer, err := NewAuthServicer(c, tc.params) + + if tc.errv != "" { + req.EqualError(err, tc.errv) + return + } + + err = authServicer.Do(rq) + + if tc.err != "" { + req.EqualError(err, tc.err) + } else { + req.Equal(tc.exp, rq.Header) + } + }) + } +} diff --git a/pkg/apigw/error.go b/pkg/apigw/error.go new file mode 100644 index 000000000..689b05458 --- /dev/null +++ b/pkg/apigw/error.go @@ -0,0 +1,18 @@ +package apigw + +import ( + "context" + "net/http" +) + +type ( + defaultErrorHandler struct{} +) + +func (h defaultErrorHandler) Exec(ctx context.Context, scope *scp, err error) { + // set http status code + scope.Writer().WriteHeader(http.StatusInternalServerError) + + // set body + scope.Writer().Write([]byte(err.Error())) +} diff --git a/pkg/apigw/expediter.go b/pkg/apigw/expediter.go index 9b9dfeb25..981aeab74 100644 --- a/pkg/apigw/expediter.go +++ b/pkg/apigw/expediter.go @@ -1,17 +1,22 @@ package apigw import ( + "bytes" "context" "encoding/json" "fmt" "net/http" - - "github.com/cortezaproject/corteza-server/system/types" - "github.com/davecgh/go-spew/spew" + "net/url" ) type ( - expediterRedirection struct{} + expediterRedirection struct { + functionMeta + params struct { + HTTPStatus int `json:"status,string"` + Location string `json:"location"` + } + } errorHandler struct { name string @@ -21,41 +26,91 @@ type ( } ) -func NewExpediterRedirection() expediterRedirection { - return expediterRedirection{} +func NewExpediterRedirection() (e *expediterRedirection) { + e = &expediterRedirection{} + + e.Step = 3 + e.Name = "expediterRedirection" + e.Label = "Redirection expediter" + e.Kind = FunctionKindExpediter + + e.Args = []*functionMetaArg{ + { + Type: "status", + Label: "status", + Options: map[string]interface{}{}, + }, + { + Type: "text", + Label: "location", + Options: map[string]interface{}{}, + }, + } + + return } -func (h expediterRedirection) Meta(f *types.Function) functionMeta { - return functionMeta{ - Step: 3, - Name: "expediterRedirection", - Label: "Redirection expediter", - Kind: "expediter", - Weight: int(f.Weight), - Params: f.Params, - } +func (h expediterRedirection) String() string { + return fmt.Sprintf("apigw function %s (%s)", h.Name, h.Label) } -func (h expediterRedirection) Handler() handlerFunc { - return func(ctx context.Context, scope *scp, params map[string]interface{}, ff functionHandler) error { - scope.Writer().Header().Add(fmt.Sprintf("step_%d", ff.step), ff.name) - http.Redirect(scope.Writer(), scope.Request(), params["location"].(string), http.StatusFound) +func (h expediterRedirection) Meta() functionMeta { + return h.functionMeta +} - return nil +func (f *expediterRedirection) Merge(params []byte) (Handler, error) { + err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&f.params) + return f, err +} + +func (h expediterRedirection) Exec(ctx context.Context, scope *scp) error { + loc, err := url.ParseRequestURI(h.params.Location) + + if err != nil { + return fmt.Errorf("could not redirect: %s", err) } + + status := h.params.HTTPStatus + + if !checkStatus("redirect", status) { + return fmt.Errorf("could not redirect: wrong status %d", status) + } + + http.Redirect(scope.Writer(), scope.Request(), loc.String(), status) + + return nil } func (pp errorHandler) Exec(ctx context.Context, scope *scp, err error) { type ( responseHelper struct { - Msg string `json:"msg"` + ErrResponse struct { + Msg string `json:"msg"` + } `json:"error"` } ) resp := responseHelper{ - Msg: err.Error(), + ErrResponse: struct { + Msg string "json:\"msg\"" + }{ + Msg: err.Error(), + }, } - spew.Dump("ERR in expediter", err, resp) + // set http status code + scope.Writer().WriteHeader(http.StatusInternalServerError) + + // set body json.NewEncoder(scope.Writer()).Encode(resp) + +} + +func checkStatus(typ string, status int) bool { + switch typ { + case "redirect": + return status >= 300 && status <= 399 + default: + return true + } } diff --git a/pkg/apigw/expediter_test.go b/pkg/apigw/expediter_test.go new file mode 100644 index 000000000..14661d858 --- /dev/null +++ b/pkg/apigw/expediter_test.go @@ -0,0 +1,74 @@ +package apigw + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_expediterRedirection(t *testing.T) { + type ( + tf struct { + name string + expr string + err string + } + ) + + var ( + tcc = []tf{ + { + name: "simple redirection", + expr: `{"status":"302", "location": "http://redire.ct/to"}`, + }, + { + name: "permanent redirection", + expr: `{"status":"301", "location": "http://redire.ct/to"}`, + }, + { + name: "url validation", + expr: `{"status":"301", "location": "invalid url"}`, + err: `could not redirect: parse "invalid url": invalid URI for request`, + }, + { + name: "invalid redirection status", + expr: `{"status":"400", "location": "http://redire.ct/to"}`, + err: "could not redirect: wrong status 400", + }, + } + ) + + for _, tc := range tcc { + var ( + ctx = context.Background() + ) + + t.Run(tc.name, func(t *testing.T) { + req := require.New(t) + + r, err := http.NewRequest(http.MethodGet, "/foo", http.NoBody) + + req.NoError(err) + + rc := httptest.NewRecorder() + scope := &scp{"request": r, "writer": rc} + + h := NewExpediterRedirection() + h.Merge([]byte(tc.expr)) + + err = h.Exec(ctx, scope) + + if tc.err != "" { + req.EqualError(err, tc.err) + return + } + + req.NoError(err) + req.Equal(h.params.Location, rc.Header().Get("Location")) + req.Equal(h.params.HTTPStatus, rc.Code) + }) + } +} diff --git a/pkg/apigw/function.go b/pkg/apigw/function.go index 586b7d0be..7b9082a23 100644 --- a/pkg/apigw/function.go +++ b/pkg/apigw/function.go @@ -1,75 +1,52 @@ package apigw -import ( - "context" - - "github.com/cortezaproject/corteza-server/system/types" +const ( + FunctionKindVerifier FunctionKind = "verifier" + FunctionKindValidator FunctionKind = "validator" + FunctionKindProcesser FunctionKind = "processer" + FunctionKindExpediter FunctionKind = "expediter" ) type ( - functionMetaList []*functionMeta + FunctionKind string Handler interface { - Handler() handlerFunc - Meta(f *types.Function) functionMeta - } + Execer + Stringer - handlerFunc func(context.Context, *scp, map[string]interface{}, functionHandler) error + Merge([]byte) (Handler, error) + Meta() functionMeta + } functionMeta struct { - Step int `json:"step"` - Weight int `json:"-"` - Name string `json:"name"` - Label string `json:"label"` - Kind string `json:"kind"` - Params map[string]interface{} `json:"-"` - Args []*functionMetaArg `json:"params,omitempty"` + Step int `json:"step"` + Weight int `json:"-"` + Name string `json:"name"` + Label string `json:"label"` + Kind FunctionKind `json:"kind"` + Args []*functionMetaArg `json:"params,omitempty"` } + functionMetaList []*functionMeta + functionMetaArg struct { Label string `json:"label"` Type string `json:"type"` Example string `json:"example"` Options map[string]interface{} `json:"options"` } - - functionHandler struct { - step int - weight int - name string - label string - kind string - handler handlerFunc - params map[string]interface{} - } ) -func (ff functionHandler) Exec(ctx context.Context, scope *scp, params map[string]interface{}) error { - return ff.handler(ctx, scope, params, ff) -} - -func (ff *functionHandler) SetHandler(h handlerFunc) { - ff.handler = h -} - -func (ff *functionHandler) Merge(ctx context.Context, p functionMeta) { - ff.step = p.Step - ff.kind = p.Kind - ff.label = p.Label - ff.name = p.Name - ff.weight = p.Weight - ff.params = p.Params -} - -func (ff functionHandler) Weight() int { - // if there's gonna be more than 1000 funcs - // per step, we're doing something wrong - return ff.step*1000 + ff.weight -} +// func (ff functionHandler) Weight() int { +// // if there's gonna be more than 1000 funcs +// // per step, we're doing something wrong +// return ff.step*1000 + ff.weight +// } func (fm functionMetaList) Filter(f func(*functionMeta) (bool, error)) (out functionMetaList, err error) { var ok bool out = functionMetaList{} + for i := range fm { if ok, err = f(fm[i]); err != nil { return diff --git a/pkg/apigw/helpers.go b/pkg/apigw/helpers.go new file mode 100644 index 000000000..62367ff72 --- /dev/null +++ b/pkg/apigw/helpers.go @@ -0,0 +1,21 @@ +package apigw + +import ( + "net/http" + + "github.com/cortezaproject/corteza-server/pkg/options" +) + +const ( + devHelperResponseBody string = `Hey developer!` +) + +func helperDefaultResponse(opt *options.ApigwOpt) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if opt.LogEnabled { + http.Error(w, devHelperResponseBody, http.StatusTeapot) + } else { + http.Error(w, ``, http.StatusFound) + } + } +} diff --git a/pkg/apigw/matcher_test.go b/pkg/apigw/matcher_test.go deleted file mode 100644 index 5a29e46e9..000000000 --- a/pkg/apigw/matcher_test.go +++ /dev/null @@ -1,74 +0,0 @@ -package apigw - -import ( - "context" - "net/http" - "testing" - - "github.com/cortezaproject/corteza-server/pkg/expr" - "github.com/stretchr/testify/require" -) - -func TestAuthenticationOriginMatcher(t *testing.T) { - type ( - tf struct { - name string - origin string - exp string - req *http.Request - } - ) - - var ( - ctx = context.Background() - - tcc = []tf{ - { - name: "fail on origin", - origin: "http://fail.ed", - exp: "workflow 0 step 0 execution failed: origin fail", - - req: &http.Request{ - Header: http.Header{ - "Origin": []string{ - "http://localhost", - }, - }, - }, - }, - { - name: "success on origin", - origin: "http://localhost", - exp: "", - - req: &http.Request{ - Header: http.Header{ - "Origin": []string{ - "http://localhost", - }, - }, - }, - }, - } - ) - - for _, tc := range tcc { - t.Run(tc.name, func(t *testing.T) { - var ( - req = require.New(t) - input = &expr.Vars{} - ) - - input.Set("origin", tc.origin) - - err := execFn(t, tc.req, authenticationOriginMatcher(ctx, input)) - - if tc.exp != "" { - req.EqualError(err, tc.exp) - } else { - req.NoError(err) - } - }) - } - -} diff --git a/pkg/apigw/pipeline.go b/pkg/apigw/pipeline.go index c2f347d31..b6712c93d 100644 --- a/pkg/apigw/pipeline.go +++ b/pkg/apigw/pipeline.go @@ -2,13 +2,16 @@ package apigw import ( "context" + "fmt" "net/http" - "sort" + + "github.com/cortezaproject/corteza-server/pkg/options" + "go.uber.org/zap" ) type ( Execer interface { - Exec(context.Context, *scp, map[string]interface{}) error + Exec(context.Context, *scp) error } Sorter interface { @@ -19,26 +22,35 @@ type ( Exec(context.Context, *scp, error) } - Payload struct { - params map[string]interface{} - worker Worker - } - Worker interface { Execer - Sorter + // Sorter + Stringer } - workers []Payload + Stringer interface { + String() string + } + + workers []Worker pl struct { w workers err ErrorHandler + log *zap.Logger } scp map[string]interface{} ) +func NewPipeline(log *zap.Logger, w ...Worker) *pl { + return &pl{ + w: w, + log: log, + err: defaultErrorHandler{}, + } +} + func (s scp) Request() *http.Request { if _, ok := s["request"]; ok { return s["request"].(*http.Request) @@ -55,19 +67,39 @@ func (s scp) Writer() http.ResponseWriter { return nil } +func (s scp) Opts() *options.ApigwOpt { + if _, ok := s["opts"]; ok { + return s["opts"].(*options.ApigwOpt) + } + + return nil +} + func (s scp) Set(k string, v interface{}) { s[k] = v } +func (s scp) Get(k string) (v interface{}, err error) { + var ok bool + + if v, ok = s[k]; !ok { + err = fmt.Errorf("could not get key on index: %s", k) + return + } + + return +} + // Exec takes care of error handling and main // functionality that takes place in worker func (pp *pl) Exec(ctx context.Context, scope *scp) (err error) { for _, w := range pp.w { - err = w.worker.Exec(ctx, scope, w.params) + + pp.log.Debug("executing worker", zap.Any("worker", w.String())) + err = w.Exec(ctx, scope) if err != nil { - // call the error handler - pp.err.Exec(ctx, scope, err) + pp.log.Debug("could not execute worker", zap.Error(err)) return } } @@ -76,10 +108,12 @@ func (pp *pl) Exec(ctx context.Context, scope *scp) (err error) { } // Add registers a new worker with parameters -// fethed from store -func (pp *pl) Add(ff Worker, p map[string]interface{}) { - pp.w = append(pp.w, Payload{worker: ff, params: p}) - sort.Sort(pp.w) +// fetched from store +func (pp *pl) Add(w Worker) { + pp.w = append(pp.w, w) + // sort.Sort(pp.w) + + pp.log.Debug("registered worker", zap.Any("worker", w.String())) } // add error handler @@ -87,8 +121,8 @@ func (pp *pl) ErrorHandler(ff ErrorHandler) { pp.err = ff } -func (a workers) Len() int { return len(a) } -func (a workers) Less(i, j int) bool { - return a[i].worker.Weight() < a[j].worker.Weight() -} -func (a workers) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +// func (a workers) Len() int { return len(a) } +// func (a workers) Less(i, j int) bool { +// return a[i].worker.Weight() < a[j].worker.Weight() +// } +// func (a workers) Swap(i, j int) { a[i], a[j] = a[j], a[i] } diff --git a/pkg/apigw/pipeline_test.go b/pkg/apigw/pipeline_test.go new file mode 100644 index 000000000..fd4520afc --- /dev/null +++ b/pkg/apigw/pipeline_test.go @@ -0,0 +1,69 @@ +package apigw + +import ( + "context" + "fmt" + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/zap" +) + +func NewPl() *pl { + return NewPipeline(zap.NewNop()) +} + +func Test_pipelineAdd(t *testing.T) { + var ( + req = require.New(t) + ) + + p := NewPl() + p.Add(mockExecer{}) + + req.Len(p.w, 1) +} + +func Test_pipelineExec(t *testing.T) { + var ( + ctx = context.Background() + req = require.New(t) + scope = &scp{"foo": 1} + ) + + p := NewPl() + p.Add(mockExecer{ + exec: func(c context.Context, s *scp) (err error) { + s.Set("foo", 2) + return nil + }, + }) + + err := p.Exec(ctx, scope) + + req.NoError(err) + + foo, err := scope.Get("foo") + + req.NoError(err) + req.Equal(2, foo) +} + +func Test_pipelineExecErr(t *testing.T) { + var ( + ctx = context.Background() + req = require.New(t) + scope = &scp{"foo": 1} + ) + + p := NewPl() + p.Add(mockExecer{ + exec: func(c context.Context, s *scp) (err error) { + return fmt.Errorf("error returned") + }, + }) + + err := p.Exec(ctx, scope) + + req.Error(err, "error returned") +} diff --git a/pkg/apigw/processer.go b/pkg/apigw/processer.go index 56fd11c11..3da66d3ef 100644 --- a/pkg/apigw/processer.go +++ b/pkg/apigw/processer.go @@ -1,12 +1,32 @@ package apigw import ( + "bytes" "context" + "encoding/json" "fmt" + "io" + "net/http" + "net/http/httputil" + "net/url" + "time" atypes "github.com/cortezaproject/corteza-server/automation/types" "github.com/cortezaproject/corteza-server/pkg/expr" - "github.com/cortezaproject/corteza-server/system/types" + "go.uber.org/zap" +) + +var ( + hopHeaders = []string{ + "Connection", + "Keep-Alive", + "Proxy-Authenticate", + "Proxy-Authorization", + "Te", + "Trailers", + "Transfer-Encoding", + "Upgrade", + } ) type ( @@ -15,93 +35,252 @@ type ( } processerWorkflow struct { + functionMeta d WfExecer + + params struct { + Workflow uint64 `json:"workflow"` + } + } + + processerProxy struct { + functionMeta + a AuthServicer + c *http.Client + log *zap.Logger + + params struct { + Location string `json:"location"` + Auth authParams `json:"auth"` + } } ) -func NewProcesserWorkflow(wf WfExecer) processerWorkflow { - return processerWorkflow{ - d: wf, - } -} +func NewProcesserWorkflow(wf WfExecer) (p *processerWorkflow) { + p = &processerWorkflow{} -func (h processerWorkflow) Meta(f *types.Function) functionMeta { - return functionMeta{ - Step: 2, - Name: "processerWorkflow", - Label: "Workflow processer", - Kind: "processer", - Weight: int(f.Weight), - Params: f.Params, - Args: []*functionMetaArg{ - { - Type: "workflow", - Label: "workflow", - Options: map[string]interface{}{}, - }, + p.d = wf + + p.Step = 2 + p.Name = "processerWorkflow" + p.Label = "Workflow processer" + p.Kind = FunctionKindProcesser + + p.Args = []*functionMetaArg{ + { + Type: "workflow", + Label: "workflow", + Options: map[string]interface{}{}, }, } + + return } -func (h processerWorkflow) Handler() handlerFunc { - return func(ctx context.Context, scope *scp, params map[string]interface{}, ff functionHandler) error { - var ( - wfID int64 - ok bool - err error - ) +func (h processerWorkflow) String() string { + return fmt.Sprintf("apigw function %s (%s)", h.Name, h.Label) +} - // validate workflow param - if _, ok = params["workflow"]; !ok { - return fmt.Errorf("invalid param workflow") - } +func (h processerWorkflow) Meta() functionMeta { + return h.functionMeta +} - wfID, err = expr.CastToInteger(params["workflow"]) +func (f *processerWorkflow) Merge(params []byte) (Handler, error) { + err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&f.params) - if err != nil { - return err - } + return f, err +} - // setup scope for workflow - vv := map[string]interface{}{ - "request": scope.Request(), - } +func (h processerWorkflow) Exec(ctx context.Context, scope *scp) error { + var ( + err error + ) - // get the request data and put it into vars - in, err := expr.NewVars(vv) + // setup scope for workflow + vv := map[string]interface{}{ + "request": scope.Request(), + } - if err != nil { - return err - } - - wp := atypes.WorkflowExecParams{ - Trace: false, - // todo depending on settings per-route - Async: false, - // todo depending on settings per-route - Wait: true, - Input: in, - } - - out, _, err := h.d.Exec(ctx, uint64(wfID), wp) - - if err != nil { - return err - } - - // merge out with scope - merged, err := in.Merge(out) - - if err != nil { - return err - } - - mm, err := expr.CastToVars(merged) - - for k, v := range mm { - scope.Set(k, v) - } + // get the request data and put it into vars + in, err := expr.NewVars(vv) + if err != nil { return err } + + wp := atypes.WorkflowExecParams{ + Trace: false, + // todo depending on settings per-route + Async: false, + // todo depending on settings per-route + Wait: true, + Input: in, + } + + out, _, err := h.d.Exec(ctx, uint64(h.params.Workflow), wp) + + if err != nil { + return err + } + + // merge out with scope + merged, err := in.Merge(out) + + if err != nil { + return err + } + + mm, err := expr.CastToVars(merged) + + for k, v := range mm { + scope.Set(k, v) + } + + return err +} + +func NewProcesserProxy(l *zap.Logger, c *http.Client) (p *processerProxy) { + p = &processerProxy{} + + p.c = c + p.log = l + + p.Step = 2 + p.Name = "processerProxy" + p.Label = "Proxy processer" + p.Kind = FunctionKindProcesser + + p.Args = []*functionMetaArg{ + { + Type: "text", + Label: "location", + Options: map[string]interface{}{}, + }, + } + + return +} + +func (h processerProxy) String() string { + return fmt.Sprintf("apigw function %s (%s)", h.Name, h.Label) +} + +func (h processerProxy) Meta() functionMeta { + return h.functionMeta +} + +func (f *processerProxy) Merge(params []byte) (Handler, error) { + err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&f.params) + + if err != nil { + return nil, err + } + + // get the auth mechanism + f.a, err = NewAuthServicer(f.c, f.params.Auth) + + if err != nil { + return nil, fmt.Errorf("could not load auth servicer for proxying: %s", err) + } + + return f, err +} + +func (h processerProxy) Exec(ctx context.Context, scope *scp) (err error) { + ctx, cancel := context.WithTimeout(ctx, scope.Opts().ProxyOutboundTimeout) + defer cancel() + + req := scope.Request() + log := h.log.With(zap.String("ref", h.Name)) + + outreq := req.Clone(ctx) + + l, err := url.ParseRequestURI(h.params.Location) + + if err != nil { + return fmt.Errorf("could not parse destination location for proxying: %s", err) + } + + // should we preserve query params? headers? post data? + outreq.URL = l + outreq.RequestURI = "" + outreq.Method = req.Method + outreq.Host = l.Hostname() + + // use authservicer, set any additional headers + err = h.a.Do(outreq) + + if err != nil { + return fmt.Errorf("errors setting auth for proxying: %s", err) + } + + // merge the old query params to the new request + // do not overwrite old ones + // do it after the authServicer, since we also may add them there + mergeQueryParams(req, outreq) + + if scope.Opts().ProxyEnableDebugLog { + o, _ := httputil.DumpRequestOut(outreq, false) + log.Debug("proxy outbound request", zap.Any("request", string(o))) + } + + // temporary metrics before the proper functionality + startTime := time.Now() + + // todo - disable / enable follow redirects, already + // added to options + resp, err := h.c.Do(outreq) + + if err != nil { + return fmt.Errorf("could not proxy request: %s", err) + } + + if scope.Opts().ProxyEnableDebugLog { + o, _ := httputil.DumpResponse(resp, false) + log.Debug("proxy outbound response", zap.Any("request", string(o)), zap.Duration("duration", time.Since(startTime))) + } + + b, err := io.ReadAll(resp.Body) + + if err != nil { + return fmt.Errorf("could not read get body on proxy request: %s", err) + } + + mergeHeaders(resp.Header, scope.Writer().Header()) + + // add to writer + scope.Writer().Write(b) + + return nil +} + +func mergeHeaders(orig, dest http.Header) { +OUTER: + for name, values := range orig { + // skip headers that need to be omitted + // when proxying + for _, v := range hopHeaders { + if v == name { + continue OUTER + } + } + dest[name] = values + } +} + +func mergeQueryParams(orig, dest *http.Request) { + origValues := dest.URL.Query() + + for k, qp := range orig.URL.Query() { + // skip existing + if dest.URL.Query().Get(k) != "" { + continue + } + + for _, v := range qp { + origValues.Add(k, v) + } + } + + dest.URL.RawQuery = origValues.Encode() } diff --git a/pkg/apigw/processer_test.go b/pkg/apigw/processer_test.go new file mode 100644 index 000000000..94476bf2c --- /dev/null +++ b/pkg/apigw/processer_test.go @@ -0,0 +1,195 @@ +package apigw + +import ( + "bytes" + "context" + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + + "github.com/cortezaproject/corteza-server/pkg/options" + "github.com/stretchr/testify/require" + "go.uber.org/zap" +) + +func Test_processerProxy(t *testing.T) { + type ( + exp struct { + Status int + Header http.Header + Body *bytes.Buffer + } + + tf struct { + name string + err string + params string + exp exp + rq *http.Request + fn func(*require.Assertions) mockRoundTripper + } + ) + + var ( + tcc = []tf{ + { + name: "proxy processer with auth headers", + fn: func(req *require.Assertions) mockRoundTripper { + return func(r *http.Request) (rs *http.Response, err error) { + rs = &http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader("default response")), + } + + return + } + }, + params: `{"location": "/foo", "auth": {"type": "header", "params": {"access-token": "123", "client": "456"}}}`, + exp: exp{ + Status: http.StatusOK, + Header: http.Header{"Content-Type": []string{"text/plain; charset=utf-8"}}, + Body: bytes.NewBufferString("default response"), + }, + }, + { + name: "proxy processer with auth query params", + fn: func(req *require.Assertions) mockRoundTripper { + return func(r *http.Request) (rs *http.Response, err error) { + rs = &http.Response{} + req.Equal("access-param=123%2B456", r.URL.RawQuery) + return + } + }, + params: `{"location": "/foo", "auth": {"type": "query", "params": {"access-param": "123+456"}}}`, + exp: exp{ + Status: http.StatusOK, + Header: http.Header{"Content-Type": []string{"text/plain; charset=utf-8"}}, + Body: bytes.NewBuffer(nil), + }, + }, + { + name: "proxy processer with auth headers unauthorized", + fn: func(req *require.Assertions) mockRoundTripper { + return func(r *http.Request) (rs *http.Response, err error) { + rs = &http.Response{ + StatusCode: http.StatusUnauthorized, + Body: io.NopCloser(strings.NewReader("unauthorized response")), + } + + return + } + }, + params: `{"location": "/foo", "auth": {"type": "header", "params": {"access-token": "123", "client": "456"}}}`, + exp: exp{ + Status: http.StatusUnauthorized, + Header: http.Header{"Content-Type": []string{"text/plain; charset=utf-8"}}, + Body: bytes.NewBufferString("unauthorized response"), + }, + }, + { + name: "proxy processer params parse error", + params: `{"location": "invalid url", "auth": {"type": "header", "params": {}}}`, + err: `could not parse destination location for proxying: parse "invalid url": invalid URI for request`, + }, + { + name: "proxy processer params request error", + fn: func(req *require.Assertions) mockRoundTripper { + return func(r *http.Request) (rs *http.Response, err error) { + err = fmt.Errorf("error on client.Do") + return + } + }, + params: `{"location": "https://example.com", "auth": {"type": "header", "params": {}}}`, + err: `could not proxy request: Post "https://example.com": error on client.Do`, + }, + { + name: "proxy processer hop headers removed", + fn: func(req *require.Assertions) mockRoundTripper { + return func(r *http.Request) (rs *http.Response, err error) { + rs = &http.Response{ + Header: http.Header{ + "Proxy-Authenticate": []string{`Basic realm="Access to the internal site"`}, + "Content-Type": []string{"application/json; charset=utf-8"}, + }, + StatusCode: http.StatusOK, + Body: io.NopCloser(strings.NewReader("default response")), + } + + return + } + }, + params: `{"location": "https://example.com", "auth": {"type": "header", "params": {}}}`, + exp: exp{ + Status: http.StatusUnauthorized, + Header: http.Header{"Content-Type": []string{"application/json; charset=utf-8"}}, + Body: bytes.NewBufferString("default response"), + }, + }, + { + name: "proxy processer query parameters merged", + fn: func(req *require.Assertions) mockRoundTripper { + return func(r *http.Request) (rs *http.Response, err error) { + rs = &http.Response{} + req.Equal("access-param=123%2B456&addedCustomQueryParam=true", r.URL.RawQuery) + return + } + }, + params: `{"location": "https://example.com", "auth": {"type": "query", "params": {"access-param": "123+456"}}}`, + exp: exp{ + Status: http.StatusUnauthorized, + Header: http.Header{"Content-Type": []string{"text/plain; charset=utf-8"}}, + Body: bytes.NewBuffer(nil), + }, + rq: &http.Request{ + Header: http.Header{}, + URL: &url.URL{Path: "/foo", RawQuery: "addedCustomQueryParam=true"}, + Body: http.NoBody, + Method: "POST", + }, + }, + } + ) + + for _, tc := range tcc { + + t.Run(tc.name, func(t *testing.T) { + var ( + ctx = context.Background() + req = require.New(t) + c = http.DefaultClient + rq = tc.rq + ) + + if tc.fn != nil { + c.Transport = mockRoundTripper(tc.fn(req)) + } + + if rq == nil { + rq, _ = http.NewRequest("POST", "/foo", strings.NewReader(`custom request body`)) + } + + proxy := NewProcesserProxy(zap.NewNop(), c) + proxy.Merge([]byte(tc.params)) + + scope := &scp{ + "request": rq, + "writer": httptest.NewRecorder(), + "opts": options.Apigw(), + } + + err := proxy.Exec(ctx, scope) + + if tc.err != "" { + req.EqualError(err, tc.err) + } else { + req.NoError(err) + req.Equal(tc.exp.Header, scope.Writer().(*httptest.ResponseRecorder).Header()) + req.Equal(tc.exp.Body, scope.Writer().(*httptest.ResponseRecorder).Body) + } + }) + } +} diff --git a/pkg/apigw/registry.go b/pkg/apigw/registry.go index 0f7e6362b..1497c72f4 100644 --- a/pkg/apigw/registry.go +++ b/pkg/apigw/registry.go @@ -2,11 +2,9 @@ package apigw import ( "fmt" + "net/http" "github.com/cortezaproject/corteza-server/automation/service" - as "github.com/cortezaproject/corteza-server/automation/service" - "github.com/cortezaproject/corteza-server/pkg/options" - "github.com/cortezaproject/corteza-server/system/types" ) type ( @@ -25,6 +23,11 @@ func (r *registry) Add(n string, h Handler) { r.h[n] = h } +func (r *registry) Merge(h Handler, b []byte) (hh Handler, err error) { + hh, err = h.Merge(b) + return +} + func (r *registry) Get(identifier string) (Handler, error) { var ( ok bool @@ -40,8 +43,8 @@ func (r *registry) Get(identifier string) (Handler, error) { func (r *registry) All() (list functionMetaList) { for _, handler := range r.h { - m := handler.Meta(&types.Function{}) - list = append(list, &m) + meta := handler.Meta() + list = append(list, &meta) } return @@ -50,10 +53,8 @@ func (r *registry) All() (list functionMetaList) { func (r *registry) Preload() { r.Add("verifierQueryParam", NewVerifierQueryParam()) r.Add("verifierOrigin", NewVerifierOrigin()) + r.Add("validatorHeader", NewValidatorHeader()) r.Add("expediterRedirection", NewExpediterRedirection()) r.Add("processerWorkflow", NewProcesserWorkflow(NewWorkflow())) -} - -func NewWorkflow() WfExecer { - return as.Workflow(service.DefaultLogger, options.CorredorOpt{}) + r.Add("processerProxy", NewProcesserProxy(service.DefaultLogger, http.DefaultClient)) } diff --git a/pkg/apigw/registry_test.go b/pkg/apigw/registry_test.go new file mode 100644 index 000000000..a6f690a6c --- /dev/null +++ b/pkg/apigw/registry_test.go @@ -0,0 +1,100 @@ +package apigw + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_registryAddGet(t *testing.T) { + var ( + req = require.New(t) + r = NewRegistry() + ) + + r.Add("mockHandler", mockHandler{}) + + h, err := r.Get("mockHandler") + + req.NoError(err) + req.Len(r.h, 1) + req.IsType(mockHandler{}, h) +} + +func Test_registryAddGetErr(t *testing.T) { + var ( + req = require.New(t) + r = NewRegistry() + ) + + r.Add("mockHandler", mockHandler{}) + + h, err := r.Get("foo") + + req.EqualError(err, "could not get element from registry: foo") + req.Len(r.h, 1) + req.Nil(h) +} + +func Test_registryMerge(t *testing.T) { + type ( + tf struct { + name string + err string + params string + exp string + } + ) + + var ( + tcc = []tf{ + { + name: "set params", + params: `{"foo":"bar"}`, + exp: "bar", + }, + { + name: "set invalid params", + params: `{"foo1":"bar"}`, + exp: "", + }, + { + name: "set invalid params err", + params: `{"foo1":"bar"`, + exp: "", + err: "unexpected EOF", + }, + } + ) + + for _, tc := range tcc { + var ( + req = require.New(t) + r = NewRegistry() + ) + + m, err := r.Merge(mockHandler{}, []byte(tc.params)) + + if tc.err != "" { + req.EqualError(err, tc.err) + } else { + req.Equal(m.(mockHandler).Foo, tc.exp) + req.NoError(err) + } + } + +} + +func Test_registryAll(t *testing.T) { + var ( + req = require.New(t) + r = NewRegistry() + ) + + r.Add("mockHandler", mockHandler{}) + + list := r.All() + + req.Len(list, 1) + req.NotEmpty(list[0].Name) +} diff --git a/pkg/apigw/route.go b/pkg/apigw/route.go index 0ee70493a..19f3e5f9e 100644 --- a/pkg/apigw/route.go +++ b/pkg/apigw/route.go @@ -1,8 +1,12 @@ package apigw import ( - "context" + "fmt" "net/http" + "net/http/httputil" + + "github.com/cortezaproject/corteza-server/pkg/options" + "go.uber.org/zap" ) type ( @@ -11,22 +15,50 @@ type ( endpoint string method string + opts *options.ApigwOpt + log *zap.Logger pipe *pl } ) func (r route) ServeHTTP(w http.ResponseWriter, req *http.Request) { var ( - ctx = context.Background() + ctx = req.Context() scope = scp{} ) - scope["request"] = req - scope["writer"] = w + scope.Set("request", req) + scope.Set("writer", w) + scope.Set("opts", r.opts) + + if err := r.validate(req); err != nil { + r.log.Debug("error validating request on route", zap.Error(err)) + r.pipe.err.Exec(ctx, &scope, fmt.Errorf("could not validate request: %s", err)) + return + } + + if r.opts.LogEnabled { + o, _ := httputil.DumpRequest(req, false) + r.log.Debug("incoming request", zap.Any("request", string(o))) + } err := r.pipe.Exec(ctx, &scope) if err != nil { - // log error + // call the error handler + r.log.Debug("calling default error handler on error") + r.pipe.err.Exec(ctx, &scope, err) } } + +func (r route) validate(req *http.Request) (err error) { + if req.Method != r.method { + err = fmt.Errorf("invalid method %s", req.Method) + } + + return +} + +func (r route) String() string { + return fmt.Sprintf("%s %s", r.method, r.endpoint) +} diff --git a/pkg/apigw/route_test.go b/pkg/apigw/route_test.go new file mode 100644 index 000000000..627417deb --- /dev/null +++ b/pkg/apigw/route_test.go @@ -0,0 +1,117 @@ +package apigw + +import ( + "context" + "errors" + "net/http" + "net/http/httptest" + "testing" + + "github.com/cortezaproject/corteza-server/pkg/options" + "github.com/stretchr/testify/require" + "go.uber.org/zap" +) + +func Test_pl(t *testing.T) { + type ( + tf struct { + name string + handler Worker + method string + errHandler ErrorHandler + expStatus int + expError error + } + ) + + var ( + tcc = []tf{ + { + name: "successful exec", + handler: mockExecer{ + exec: func(c context.Context, s *scp) (err error) { + s.Writer().WriteHeader(http.StatusTemporaryRedirect) + return + }, + }, + errHandler: mockErrorExecer{ + exec: func(c context.Context, s *scp, e error) { + s.Writer().Write([]byte(e.Error())) + }, + }, + method: "POST", + expStatus: http.StatusTemporaryRedirect, + expError: nil, + }, + { + name: "unsuccessful exec", + handler: mockExecer{ + exec: func(c context.Context, s *scp) (err error) { + s.Writer().WriteHeader(http.StatusTemporaryRedirect) + return errors.New("test error") + }, + }, + errHandler: mockErrorExecer{ + exec: func(c context.Context, s *scp, e error) { + s.Writer().WriteHeader(http.StatusInternalServerError) + s.Writer().Write([]byte(e.Error())) + }, + }, + method: "POST", + expStatus: http.StatusTemporaryRedirect, + expError: errors.New("test error"), + }, + { + name: "request method validation fail", + handler: mockExecer{ + exec: func(c context.Context, s *scp) (err error) { + s.Writer().WriteHeader(http.StatusTemporaryRedirect) + return errors.New("test error") + }, + }, + errHandler: mockErrorExecer{ + exec: func(c context.Context, s *scp, e error) { + s.Writer().WriteHeader(http.StatusInternalServerError) + s.Writer().Write([]byte(e.Error())) + }, + }, + method: "GET", + expStatus: http.StatusInternalServerError, + expError: errors.New("could not validate request: invalid method POST"), + }, + } + ) + + for _, tc := range tcc { + t.Run(tc.name, func(t *testing.T) { + var ( + req = require.New(t) + rr = httptest.NewRecorder() + pipe = NewPl() + ) + + r, err := http.NewRequest("POST", "/foo", http.NoBody) + req.NoError(err) + + pipe.Add(tc.handler) + pipe.ErrorHandler(tc.errHandler) + + route := &route{ + method: tc.method, + pipe: pipe, + log: zap.NewNop(), + opts: options.Apigw(), + } + + route.ServeHTTP(rr, r) + + expError := "" + if tc.expError != nil { + expError = tc.expError.Error() + } + + req.Equal(tc.expStatus, rr.Result().StatusCode) + req.Equal(expError, rr.Body.String()) + }) + } +} diff --git a/pkg/apigw/test.go b/pkg/apigw/test.go new file mode 100644 index 000000000..21bb6a7b6 --- /dev/null +++ b/pkg/apigw/test.go @@ -0,0 +1,67 @@ +package apigw + +import ( + "bytes" + "context" + "encoding/json" + + "github.com/cortezaproject/corteza-server/system/types" +) + +type ( + mockExecer struct { + exec func(context.Context, *scp) (err error) + } + + mockErrorExecer struct { + exec func(context.Context, *scp, error) + } + + mockHandler struct { + Foo string `json:"foo"` + } + + mockStorer struct { + f func(context.Context, types.ApigwFunctionFilter) (types.ApigwFunctionSet, types.ApigwFunctionFilter, error) + r func(context.Context, types.ApigwRouteFilter) (types.ApigwRouteSet, types.ApigwRouteFilter, error) + } +) + +func (h mockHandler) String() string { + return "mockHandler" +} + +func (h mockHandler) Exec(_ context.Context, _ *scp) error { + panic("not implemented") // TODO: Implement +} + +func (h mockHandler) Merge(params []byte) (Handler, error) { + err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&h) + return h, err +} + +func (h mockHandler) Meta() functionMeta { + return functionMeta{ + Name: "return mocked function metadata", + } +} + +func (td mockStorer) SearchApigwRoutes(ctx context.Context, f types.ApigwRouteFilter) (s types.ApigwRouteSet, ff types.ApigwRouteFilter, err error) { + return td.r(ctx, f) +} + +func (td mockStorer) SearchApigwFunctions(ctx context.Context, f types.ApigwFunctionFilter) (s types.ApigwFunctionSet, ff types.ApigwFunctionFilter, err error) { + return td.f(ctx, f) +} + +func (me mockExecer) String() string { + return "mockExecer" +} + +func (me mockExecer) Exec(ctx context.Context, s *scp) (err error) { + return me.exec(ctx, s) +} + +func (me mockErrorExecer) Exec(ctx context.Context, s *scp, e error) { + me.exec(ctx, s, e) +} diff --git a/pkg/apigw/validator.go b/pkg/apigw/validator.go new file mode 100644 index 000000000..611c5662c --- /dev/null +++ b/pkg/apigw/validator.go @@ -0,0 +1,87 @@ +package apigw + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + + "github.com/cortezaproject/corteza-server/pkg/expr" +) + +type ( + validatorHeader struct { + functionMeta + params struct { + Expr string `json:"expr"` + } + } +) + +func NewValidatorHeader() (v *validatorHeader) { + v = &validatorHeader{} + + v.Step = 3 + v.Name = "validatorHeader" + v.Label = "Header validator" + v.Kind = FunctionKindValidator + + v.Args = []*functionMetaArg{ + { + Type: "expr", + Label: "expr", + Options: map[string]interface{}{}, + }, + } + + return +} + +func (h validatorHeader) String() string { + return fmt.Sprintf("apigw function %s (%s)", h.Name, h.Label) +} + +func (h validatorHeader) Meta() functionMeta { + return h.functionMeta +} + +func (v *validatorHeader) Merge(params []byte) (Handler, error) { + err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&v.params) + return v, err +} + +func (h validatorHeader) Exec(ctx context.Context, scope *scp) error { + vv := map[string]interface{}{} + headers := scope.Request().Header + + for k, v := range headers { + // sanitize header keys? + vv[k] = v[0] + } + + // get the request data and put it into vars + out, err := expr.NewVars(vv) + + if err != nil { + return err + } + + pp := expr.NewParser() + tt, err := pp.Parse(h.params.Expr) + + if err != nil { + return fmt.Errorf("could not parse matching expression: %s", err) + } + + b, err := tt.Test(ctx, out) + + if err != nil { + return fmt.Errorf("could not validate headers: %s", err) + } + + if !b { + return fmt.Errorf("could not validate headers") + } + + return nil +} diff --git a/pkg/apigw/validator_test.go b/pkg/apigw/validator_test.go index d74b4bb47..36b63cf59 100644 --- a/pkg/apigw/validator_test.go +++ b/pkg/apigw/validator_test.go @@ -3,58 +3,75 @@ package apigw import ( "context" "net/http" - "net/http/httptest" - "strings" "testing" - "github.com/cortezaproject/corteza-server/pkg/expr" "github.com/stretchr/testify/require" ) -func TestContentLengthValidator(t *testing.T) { +func Test_validatorHeader(t *testing.T) { type ( tf struct { - name string - limit int - exp string - body string + name string + expr string + err string + headers http.Header } ) var ( - ctx = context.Background() - tcc = []tf{ { - name: "fail on content length > limit", - limit: 10, - exp: "workflow 0 step 0 execution failed: content length overriden", - body: "A message that is 31 bytes long", + name: "matching simple", + expr: `{"expr":"foo == \"bar\""}`, + headers: map[string][]string{"foo": {"bar"}}, }, { - name: "success on content length < limit", - limit: 10, - exp: "", - body: "Below 10", + name: "matching case", + expr: `{"expr":"Foo == \"bar\""}`, + headers: map[string][]string{"Foo": {"bar"}}, + }, + { + name: "non matching value", + expr: `{"expr":"Foo == \"bar1\""}`, + headers: map[string][]string{"Foo": {"bar"}}, + err: "could not validate headers", + }, + { + name: "non matching key", + expr: `{"expr":"Foo1 == \"bar\""}`, + headers: map[string][]string{"Foo": {"bar"}}, + err: "could not validate headers: failed to select 'Foo1' on *expr.Vars: no such key 'Foo1'", + }, + { + name: "matching header with hyphen - TODO", + expr: `{"expr":"Content-type == \"application/json\""}`, + headers: map[string][]string{"Content-type": {"application/json"}}, }, } ) for _, tc := range tcc { + var ( + ctx = context.Background() + ) + t.Run(tc.name, func(t *testing.T) { - var ( - req = require.New(t) - input = &expr.Vars{} - ) + req := require.New(t) - input.Set("length", tc.limit) + r, err := http.NewRequest(http.MethodGet, "/foo", http.NoBody) + r.Header = tc.headers - r := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(tc.body)) + req.NoError(err) - err := execFn(t, r, contentLengthValidator(ctx, input)) + scope := &scp{"request": r} - if tc.exp != "" { - req.EqualError(err, tc.exp) + h := NewValidatorHeader() + h.Merge([]byte(tc.expr)) + + err = h.Exec(ctx, scope) + + if tc.err != "" { + req.EqualError(err, tc.err) } else { req.NoError(err) } diff --git a/pkg/apigw/verifier.go b/pkg/apigw/verifier.go index c20a65bcb..c3132b7c5 100644 --- a/pkg/apigw/verifier.go +++ b/pkg/apigw/verifier.go @@ -1,165 +1,161 @@ package apigw import ( + "bytes" "context" + "encoding/json" "fmt" "github.com/cortezaproject/corteza-server/pkg/expr" - "github.com/cortezaproject/corteza-server/system/types" - "github.com/davecgh/go-spew/spew" ) type ( - verifierQueryParam struct{} - verifierOrigin struct{} + verifierQueryParam struct { + functionMeta + params struct { + Expr string `json:"expr"` + } + } + + verifierOrigin struct { + functionMeta + params struct { + Expr string `json:"expr"` + } + } ) -func NewVerifierOrigin() verifierOrigin { - return verifierOrigin{} -} +func NewVerifierOrigin() (v *verifierOrigin) { + v = &verifierOrigin{} -func NewVerifierQueryParam() verifierQueryParam { - return verifierQueryParam{} -} + v.Step = 0 + v.Name = "verifierOrigin" + v.Label = "Origin verifier" + v.Kind = FunctionKindVerifier -func (h verifierQueryParam) Meta(f *types.Function) functionMeta { - return functionMeta{ - Step: 0, - Name: "verifierQueryParam", - Label: "Query parameters verifier", - Kind: "verifier", - Weight: int(f.Weight), - Params: f.Params, - Args: []*functionMetaArg{ - { - Type: "expr", - Label: "expr", - Options: map[string]interface{}{}, - }, + v.Args = []*functionMetaArg{ + { + Type: "expr", + Label: "expr", + Options: map[string]interface{}{}, }, } + + return } -func (h verifierOrigin) Meta(f *types.Function) functionMeta { - return functionMeta{ - Step: 0, - Name: "verifierOrigin", - Label: "Origin verifier", - Kind: "verifier", - Weight: int(f.Weight), - Params: f.Params, - Args: []*functionMetaArg{ - { - Type: "expr", - Label: "expr", - Options: map[string]interface{}{}, - }, +func (h verifierOrigin) String() string { + return fmt.Sprintf("apigw function %s (%s)", h.Name, h.Label) +} + +func (h verifierOrigin) Meta() functionMeta { + return h.functionMeta +} + +func (v *verifierOrigin) Merge(params []byte) (Handler, error) { + err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&v.params) + return v, err +} + +func (h verifierOrigin) Exec(ctx context.Context, scope *scp) error { + vv := map[string]interface{}{ + "origin": scope.Request().Header.Get("Origin"), + } + + // get the request data and put it into vars + out, err := expr.NewVars(vv) + + if err != nil { + return err + } + // spew.Dump("OUT", out) + pp := expr.NewParser() + tt, err := pp.Parse(h.params.Expr) + + if err != nil { + return fmt.Errorf("could not parse matching expression: %s", err) + } + + b, err := tt.Test(ctx, out) + + if err != nil { + return fmt.Errorf("could not validate origin: %s", err) + } + + if !b { + return fmt.Errorf("could not validate origin") + } + + return nil +} + +func NewVerifierQueryParam() (v *verifierQueryParam) { + v = &verifierQueryParam{} + + v.Step = 0 + v.Name = "verifierQueryParam" + v.Label = "Query parameters verifier" + v.Kind = "verifier" + + v.Args = []*functionMetaArg{ + { + Type: "expr", + Label: "expr", + Options: map[string]interface{}{}, }, } + + return } -func (h verifierQueryParam) Handler() handlerFunc { - return func(ctx context.Context, scope *scp, params map[string]interface{}, ff functionHandler) error { - for k := range ff.params { +func (h verifierQueryParam) String() string { + return fmt.Sprintf("apigw function %s (%s)", h.Name, h.Label) +} - v, ok := params[k] +func (h verifierQueryParam) Meta() functionMeta { + return h.functionMeta +} - if !ok { - spew.Dump("not in params", k) - continue - } +func (v *verifierQueryParam) Merge(params []byte) (Handler, error) { + err := json.NewDecoder(bytes.NewBuffer(params)).Decode(&v.params) + return v, err +} - vv := map[string]interface{}{} - vals := scope.Request().URL.Query() +func (h verifierQueryParam) Exec(ctx context.Context, scope *scp) error { + vv := map[string]interface{}{} + vals := scope.Request().URL.Query() - for k, v := range vals { - vv[k] = v[0] - } - - // get the request data and put it into vars - out, err := expr.NewVars(vv) - - if err != nil { - // spew.Dump("ERR!", err) - return err - } - - pp := expr.NewParser() - tt, err := pp.Parse(v.(string)) - - if err != nil { - // spew.Dump("ERR!", err) - return err - } - - b, err := tt.Test(ctx, out) - - if err != nil { - // spew.Dump("ERR!", err) - return err - } - - spew.Dump("BBBB", b) - - if !b { - return fmt.Errorf("failed on step %d, function %s", ff.step, ff.name) - } - } - - // testing - scope.Request().Header.Add(fmt.Sprintf("step_%d", ff.step), ff.name) - - return nil + for k, v := range vals { + vv[k] = v[0] } -} -func (h verifierOrigin) Handler() handlerFunc { - return func(ctx context.Context, scope *scp, params map[string]interface{}, ff functionHandler) error { - for k := range ff.params { - v, ok := params[k] + // get the request data and put it into vars + out, err := expr.NewVars(vv) - if !ok { - spew.Dump("not in params", k) - continue - } - - vv := map[string]interface{}{ - "origin": scope.Request().Header.Get("Origin"), - } - - // get the request data and put it into vars - out, err := expr.NewVars(vv) - - if err != nil { - spew.Dump("ERR!", err) - return err - } - - pp := expr.NewParser() - tt, err := pp.Parse(v.(string)) - - if err != nil { - spew.Dump("ERR!", err) - return err - } - - b, err := tt.Test(ctx, out) - - if err != nil { - spew.Dump("ERR!", err) - return err - } - - spew.Dump("BBBB", b) - - if !b { - return fmt.Errorf("failed on step %d, function %s", ff.step, ff.name) - } - } - - // testing - scope.Request().Header.Add(fmt.Sprintf("step_%d", ff.step), ff.name) - - return nil + if err != nil { + return err } + + pp := expr.NewParser() + tt, err := pp.Parse(h.params.Expr) + + if err != nil { + return fmt.Errorf("could not parse matching expression: %s", err) + } + + b, err := tt.Test(ctx, out) + + if err != nil { + return fmt.Errorf("could not validate query params: %s", err) + } + + if !b { + return fmt.Errorf("could not validate query params") + } + // } + + // testing + scope.Request().Header.Add(fmt.Sprintf("step_%d", h.Step), h.Name) + + return nil } diff --git a/pkg/apigw/verifier_test.go b/pkg/apigw/verifier_test.go new file mode 100644 index 000000000..3e396d32b --- /dev/null +++ b/pkg/apigw/verifier_test.go @@ -0,0 +1,141 @@ +package apigw + +import ( + "context" + "net/http" + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_verifierQueryParam(t *testing.T) { + type ( + tf struct { + name string + expr string + err string + url string + } + ) + + var ( + tcc = []tf{ + { + name: "matching simple query parameter", + expr: `{"expr":"foo == \"bar\""}`, + url: "https://examp.le?foo=bar", + }, + { + name: "matching simple query parameter - invalid expression key", + expr: `{"expr1":"foo == \"bar\""}`, + url: "https://examp.le?foo=bar", + err: "could not parse matching expression: parsing error: - 1:1 unexpected EOF while scanning extensions", + }, + { + name: "matching simple query parameter - missing value", + expr: `{"expr":"foo == \"bar\""}`, + url: "https://examp.le?foo=bar1", + err: "could not validate query params", + }, + { + name: "matching simple query parameter - missing value", + expr: `{"expr":"foo == \"bar-baz\""}`, + url: "https://examp.le?foo=bar-baz", + }, + } + ) + + for _, tc := range tcc { + var ( + ctx = context.Background() + ) + + t.Run(tc.name, func(t *testing.T) { + req := require.New(t) + + r, err := http.NewRequest(http.MethodGet, tc.url, http.NoBody) + + req.NoError(err) + + scope := &scp{"request": r} + + h := NewVerifierQueryParam() + h.Merge([]byte(tc.expr)) + + err = h.Exec(ctx, scope) + + if tc.err != "" { + req.EqualError(err, tc.err) + } else { + req.NoError(err) + } + }) + } +} + +func Test_verifierOrigin(t *testing.T) { + type ( + tf struct { + name string + expr string + err string + o string + } + ) + + var ( + tcc = []tf{ + { + name: "matching simple origin value", + expr: `{"expr":"origin == \"https://www.google.com\""}`, + o: "https://www.google.com", + }, + { + name: "matching simple nonexistent origin value", + expr: `{"expr":"origin == \"https://www.google.com\""}`, + o: "", + err: "could not validate origin", + }, + { + name: "matching simple origin value - invalid expression key", + expr: `{"expr1":"origin == \"https://www.google.com\""}`, + o: "", + err: "could not parse matching expression: parsing error: \t - 1:1 unexpected EOF while scanning extensions", + }, + { + name: "matching simple origin value - invalid expression key", + expr: `{"expr1":"origin == \"https"}`, + o: "", + err: "could not parse matching expression: parsing error: \t - 1:1 unexpected EOF while scanning extensions", + }, + } + ) + + for _, tc := range tcc { + var ( + ctx = context.Background() + ) + + t.Run(tc.name, func(t *testing.T) { + req := require.New(t) + + r, err := http.NewRequest(http.MethodGet, "/foo", http.NoBody) + r.Header.Set("Origin", tc.o) + + req.NoError(err) + + scope := &scp{"request": r} + + h := NewVerifierOrigin() + h.Merge([]byte(tc.expr)) + + err = h.Exec(ctx, scope) + + if tc.err != "" { + req.EqualError(err, tc.err) + } else { + req.NoError(err) + } + }) + } +} diff --git a/pkg/envoy/resource/rbac_references_system.gen.go b/pkg/envoy/resource/rbac_references_system.gen.go index 865d8aea4..352b0025c 100644 --- a/pkg/envoy/resource/rbac_references_system.gen.go +++ b/pkg/envoy/resource/rbac_references_system.gen.go @@ -7,10 +7,11 @@ package resource // // Definitions file that controls how this file is generated: +// - system.apigw-function.yaml +// - system.apigw-route.yaml // - system.application.yaml // - system.auth-client.yaml // - system.role.yaml -// - system.route.yaml // - system.template.yaml // - system.user.yaml // - system.yaml @@ -19,6 +20,32 @@ import ( "github.com/cortezaproject/corteza-server/system/types" ) +// SystemApigwFunctionRbacReferences generates RBAC references +// +// Resources with "envoy: false" are skipped +// +// This function is auto-generated +func SystemApigwFunctionRbacReferences(apigwFunction string) (res *Ref, pp []*Ref, err error) { + if apigwFunction != "*" { + res = &Ref{ResourceType: types.ApigwFunctionResourceType, Identifiers: MakeIdentifiers(apigwFunction)} + } + + return +} + +// SystemApigwRouteRbacReferences generates RBAC references +// +// Resources with "envoy: false" are skipped +// +// This function is auto-generated +func SystemApigwRouteRbacReferences(apigwRoute string) (res *Ref, pp []*Ref, err error) { + if apigwRoute != "*" { + res = &Ref{ResourceType: types.ApigwRouteResourceType, Identifiers: MakeIdentifiers(apigwRoute)} + } + + return +} + // SystemApplicationRbacReferences generates RBAC references // // Resources with "envoy: false" are skipped @@ -58,19 +85,6 @@ func SystemRoleRbacReferences(role string) (res *Ref, pp []*Ref, err error) { return } -// SystemRouteRbacReferences generates RBAC references -// -// Resources with "envoy: false" are skipped -// -// This function is auto-generated -func SystemRouteRbacReferences(route string) (res *Ref, pp []*Ref, err error) { - if route != "*" { - res = &Ref{ResourceType: types.RouteResourceType, Identifiers: MakeIdentifiers(route)} - } - - return -} - // SystemTemplateRbacReferences generates RBAC references // // Resources with "envoy: false" are skipped diff --git a/pkg/envoy/resource/rbac_rules_parse.gen.go b/pkg/envoy/resource/rbac_rules_parse.gen.go index 8960d891d..153cbe3e5 100644 --- a/pkg/envoy/resource/rbac_rules_parse.gen.go +++ b/pkg/envoy/resource/rbac_rules_parse.gen.go @@ -16,10 +16,11 @@ package resource // - compose.page.yaml // - compose.record.yaml // - compose.yaml +// - system.apigw-function.yaml +// - system.apigw-route.yaml // - system.application.yaml // - system.auth-client.yaml // - system.role.yaml -// - system.route.yaml // - system.template.yaml // - system.user.yaml // - system.yaml @@ -169,6 +170,26 @@ func ParseRule(res string) (string, *Ref, []*Ref, error) { // Component resource, no path return composeTypes.ComponentResourceType, nil, nil, nil + case systemTypes.ApigwFunctionResourceType: + if len(path) != 1 { + return "", nil, nil, fmt.Errorf("expecting 1 reference components in path, got %d", len(path)) + } + ref, pp, err := SystemApigwFunctionRbacReferences( + // apigwFunction + path[0], + ) + return systemTypes.ApigwFunctionResourceType, ref, pp, err + + case systemTypes.ApigwRouteResourceType: + if len(path) != 1 { + return "", nil, nil, fmt.Errorf("expecting 1 reference components in path, got %d", len(path)) + } + ref, pp, err := SystemApigwRouteRbacReferences( + // apigwRoute + path[0], + ) + return systemTypes.ApigwRouteResourceType, ref, pp, err + case systemTypes.ApplicationResourceType: if len(path) != 1 { return "", nil, nil, fmt.Errorf("expecting 1 reference components in path, got %d", len(path)) @@ -199,16 +220,6 @@ func ParseRule(res string) (string, *Ref, []*Ref, error) { ) return systemTypes.RoleResourceType, ref, pp, err - case systemTypes.RouteResourceType: - if len(path) != 1 { - return "", nil, nil, fmt.Errorf("expecting 1 reference components in path, got %d", len(path)) - } - ref, pp, err := SystemRouteRbacReferences( - // route - path[0], - ) - return systemTypes.RouteResourceType, ref, pp, err - case systemTypes.TemplateResourceType: if len(path) != 1 { return "", nil, nil, fmt.Errorf("expecting 1 reference components in path, got %d", len(path)) diff --git a/pkg/options/apigw.gen.go b/pkg/options/apigw.gen.go new file mode 100644 index 000000000..5be616bec --- /dev/null +++ b/pkg/options/apigw.gen.go @@ -0,0 +1,47 @@ +package options + +// This file is auto-generated. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// Definitions file that controls how this file is generated: +// pkg/options/apigw.yaml + +import ( + "time" +) + +type ( + ApigwOpt struct { + Enabled bool `env:"APIGW_ENABLED"` + LogEnabled bool `env:"APIGW_LOG_ENABLED"` + ProxyEnableDebugLog bool `env:"APIGW_PROXY_ENABLE_DEBUG_LOG"` + ProxyFollowRedirects bool `env:"APIGW_PROXY_FOLLOW_REDIRECTS"` + ProxyOutboundTimeout time.Duration `env:"APIGW_PROXY_OUTBOUND_TIMEOUT"` + } +) + +// Apigw initializes and returns a ApigwOpt with default values +func Apigw() (o *ApigwOpt) { + o = &ApigwOpt{ + Enabled: true, + LogEnabled: false, + ProxyEnableDebugLog: false, + ProxyFollowRedirects: true, + ProxyOutboundTimeout: time.Second * 30, + } + + fill(o) + + // Function that allows access to custom logic inside the parent function. + // The custom logic in the other file should be like: + // func (o *Apigw) Defaults() {...} + func(o interface{}) { + if def, ok := o.(interface{ Defaults() }); ok { + def.Defaults() + } + }(o) + + return +} diff --git a/pkg/options/apigw.yaml b/pkg/options/apigw.yaml new file mode 100644 index 000000000..6d047e0d1 --- /dev/null +++ b/pkg/options/apigw.yaml @@ -0,0 +1,37 @@ +docs: + title: API Gateway + +imports: + - time + +props: + - name: Enabled + type: bool + default: true + description: |- + Enable API Gateway + + - name: logEnabled + type: bool + default: false + description: |- + Enable extra logging + + - name: proxyEnableDebugLog + type: bool + default: false + description: |- + Enable full debug log on requests / responses - warning, includes sensitive data + + - name: proxyFollowRedirects + type: bool + default: true + description: |- + Follow redirects on proxy requests + + - name: proxyOutboundTimeout + type: time.Duration + default: time.Second * 30 + description: |- + Outbound request timeout + diff --git a/store/apigw_function.gen.go b/store/apigw_function.gen.go index 04bedd6fc..49b4164fe 100644 --- a/store/apigw_function.gen.go +++ b/store/apigw_function.gen.go @@ -15,51 +15,51 @@ import ( type ( ApigwFunctions interface { - SearchApigwFunctions(ctx context.Context, f types.FunctionFilter) (types.FunctionSet, types.FunctionFilter, error) - LookupApigwFunctionByID(ctx context.Context, id uint64) (*types.Function, error) - LookupApigwFunctionByRoute(ctx context.Context, route string) (*types.Function, error) + SearchApigwFunctions(ctx context.Context, f types.ApigwFunctionFilter) (types.ApigwFunctionSet, types.ApigwFunctionFilter, error) + LookupApigwFunctionByID(ctx context.Context, id uint64) (*types.ApigwFunction, error) + LookupApigwFunctionByRoute(ctx context.Context, route string) (*types.ApigwFunction, error) - CreateApigwFunction(ctx context.Context, rr ...*types.Function) error + CreateApigwFunction(ctx context.Context, rr ...*types.ApigwFunction) error - UpdateApigwFunction(ctx context.Context, rr ...*types.Function) error + UpdateApigwFunction(ctx context.Context, rr ...*types.ApigwFunction) error - DeleteApigwFunction(ctx context.Context, rr ...*types.Function) error + DeleteApigwFunction(ctx context.Context, rr ...*types.ApigwFunction) error DeleteApigwFunctionByID(ctx context.Context, ID uint64) error TruncateApigwFunctions(ctx context.Context) error } ) -var _ *types.Function +var _ *types.ApigwFunction var _ context.Context // SearchApigwFunctions returns all matching ApigwFunctions from store -func SearchApigwFunctions(ctx context.Context, s ApigwFunctions, f types.FunctionFilter) (types.FunctionSet, types.FunctionFilter, error) { +func SearchApigwFunctions(ctx context.Context, s ApigwFunctions, f types.ApigwFunctionFilter) (types.ApigwFunctionSet, types.ApigwFunctionFilter, error) { return s.SearchApigwFunctions(ctx, f) } // LookupApigwFunctionByID searches for function by ID -func LookupApigwFunctionByID(ctx context.Context, s ApigwFunctions, id uint64) (*types.Function, error) { +func LookupApigwFunctionByID(ctx context.Context, s ApigwFunctions, id uint64) (*types.ApigwFunction, error) { return s.LookupApigwFunctionByID(ctx, id) } // LookupApigwFunctionByRoute searches for function by route -func LookupApigwFunctionByRoute(ctx context.Context, s ApigwFunctions, route string) (*types.Function, error) { +func LookupApigwFunctionByRoute(ctx context.Context, s ApigwFunctions, route string) (*types.ApigwFunction, error) { return s.LookupApigwFunctionByRoute(ctx, route) } // CreateApigwFunction creates one or more ApigwFunctions in store -func CreateApigwFunction(ctx context.Context, s ApigwFunctions, rr ...*types.Function) error { +func CreateApigwFunction(ctx context.Context, s ApigwFunctions, rr ...*types.ApigwFunction) error { return s.CreateApigwFunction(ctx, rr...) } // UpdateApigwFunction updates one or more (existing) ApigwFunctions in store -func UpdateApigwFunction(ctx context.Context, s ApigwFunctions, rr ...*types.Function) error { +func UpdateApigwFunction(ctx context.Context, s ApigwFunctions, rr ...*types.ApigwFunction) error { return s.UpdateApigwFunction(ctx, rr...) } // DeleteApigwFunction Deletes one or more ApigwFunctions from store -func DeleteApigwFunction(ctx context.Context, s ApigwFunctions, rr ...*types.Function) error { +func DeleteApigwFunction(ctx context.Context, s ApigwFunctions, rr ...*types.ApigwFunction) error { return s.DeleteApigwFunction(ctx, rr...) } diff --git a/store/apigw_function.yaml b/store/apigw_function.yaml index 80ae1bcb8..6845b0af2 100644 --- a/store/apigw_function.yaml +++ b/store/apigw_function.yaml @@ -3,8 +3,8 @@ import: types: package: types - type: types.Function - filterType: types.FunctionFilter + type: types.ApigwFunction + filterType: types.ApigwFunctionFilter fields: - { field: ID, sortable: false } diff --git a/store/apigw_route.gen.go b/store/apigw_route.gen.go index fec22452c..2af69fdff 100644 --- a/store/apigw_route.gen.go +++ b/store/apigw_route.gen.go @@ -15,51 +15,51 @@ import ( type ( ApigwRoutes interface { - SearchApigwRoutes(ctx context.Context, f types.RouteFilter) (types.RouteSet, types.RouteFilter, error) - LookupApigwRouteByID(ctx context.Context, id uint64) (*types.Route, error) - LookupApigwRouteByEndpoint(ctx context.Context, endpoint string) (*types.Route, error) + SearchApigwRoutes(ctx context.Context, f types.ApigwRouteFilter) (types.ApigwRouteSet, types.ApigwRouteFilter, error) + LookupApigwRouteByID(ctx context.Context, id uint64) (*types.ApigwRoute, error) + LookupApigwRouteByEndpoint(ctx context.Context, endpoint string) (*types.ApigwRoute, error) - CreateApigwRoute(ctx context.Context, rr ...*types.Route) error + CreateApigwRoute(ctx context.Context, rr ...*types.ApigwRoute) error - UpdateApigwRoute(ctx context.Context, rr ...*types.Route) error + UpdateApigwRoute(ctx context.Context, rr ...*types.ApigwRoute) error - DeleteApigwRoute(ctx context.Context, rr ...*types.Route) error + DeleteApigwRoute(ctx context.Context, rr ...*types.ApigwRoute) error DeleteApigwRouteByID(ctx context.Context, ID uint64) error TruncateApigwRoutes(ctx context.Context) error } ) -var _ *types.Route +var _ *types.ApigwRoute var _ context.Context // SearchApigwRoutes returns all matching ApigwRoutes from store -func SearchApigwRoutes(ctx context.Context, s ApigwRoutes, f types.RouteFilter) (types.RouteSet, types.RouteFilter, error) { +func SearchApigwRoutes(ctx context.Context, s ApigwRoutes, f types.ApigwRouteFilter) (types.ApigwRouteSet, types.ApigwRouteFilter, error) { return s.SearchApigwRoutes(ctx, f) } // LookupApigwRouteByID searches for route by ID -func LookupApigwRouteByID(ctx context.Context, s ApigwRoutes, id uint64) (*types.Route, error) { +func LookupApigwRouteByID(ctx context.Context, s ApigwRoutes, id uint64) (*types.ApigwRoute, error) { return s.LookupApigwRouteByID(ctx, id) } // LookupApigwRouteByEndpoint searches for route by endpoint -func LookupApigwRouteByEndpoint(ctx context.Context, s ApigwRoutes, endpoint string) (*types.Route, error) { +func LookupApigwRouteByEndpoint(ctx context.Context, s ApigwRoutes, endpoint string) (*types.ApigwRoute, error) { return s.LookupApigwRouteByEndpoint(ctx, endpoint) } // CreateApigwRoute creates one or more ApigwRoutes in store -func CreateApigwRoute(ctx context.Context, s ApigwRoutes, rr ...*types.Route) error { +func CreateApigwRoute(ctx context.Context, s ApigwRoutes, rr ...*types.ApigwRoute) error { return s.CreateApigwRoute(ctx, rr...) } // UpdateApigwRoute updates one or more (existing) ApigwRoutes in store -func UpdateApigwRoute(ctx context.Context, s ApigwRoutes, rr ...*types.Route) error { +func UpdateApigwRoute(ctx context.Context, s ApigwRoutes, rr ...*types.ApigwRoute) error { return s.UpdateApigwRoute(ctx, rr...) } // DeleteApigwRoute Deletes one or more ApigwRoutes from store -func DeleteApigwRoute(ctx context.Context, s ApigwRoutes, rr ...*types.Route) error { +func DeleteApigwRoute(ctx context.Context, s ApigwRoutes, rr ...*types.ApigwRoute) error { return s.DeleteApigwRoute(ctx, rr...) } diff --git a/store/apigw_route.yaml b/store/apigw_route.yaml index 5cf9d9f54..3b40d2353 100644 --- a/store/apigw_route.yaml +++ b/store/apigw_route.yaml @@ -3,8 +3,8 @@ import: types: package: types - type: types.Route - filterType: types.RouteFilter + type: types.ApigwRoute + filterType: types.ApigwRouteFilter fields: - { field: ID, sortable: false } diff --git a/store/rdbms/apigw_function.gen.go b/store/rdbms/apigw_function.gen.go index 298ea4504..0f7dadb64 100644 --- a/store/rdbms/apigw_function.gen.go +++ b/store/rdbms/apigw_function.gen.go @@ -24,11 +24,11 @@ var _ = errors.Is // SearchApigwFunctions returns all matching rows // // This function calls convertApigwFunctionFilter with the given -// types.FunctionFilter and expects to receive a working squirrel.SelectBuilder -func (s Store) SearchApigwFunctions(ctx context.Context, f types.FunctionFilter) (types.FunctionSet, types.FunctionFilter, error) { +// types.ApigwFunctionFilter and expects to receive a working squirrel.SelectBuilder +func (s Store) SearchApigwFunctions(ctx context.Context, f types.ApigwFunctionFilter) (types.ApigwFunctionSet, types.ApigwFunctionFilter, error) { var ( err error - set []*types.Function + set []*types.ApigwFunction q squirrel.SelectBuilder ) @@ -112,11 +112,11 @@ func (s Store) fetchFullPageOfApigwFunctions( sort filter.SortExprSet, cursor *filter.PagingCursor, reqItems uint, - check func(*types.Function) (bool, error), + check func(*types.ApigwFunction) (bool, error), cursorCond func(*filter.PagingCursor) squirrel.Sqlizer, -) (set []*types.Function, prev, next *filter.PagingCursor, err error) { +) (set []*types.ApigwFunction, prev, next *filter.PagingCursor, err error) { var ( - aux []*types.Function + aux []*types.ApigwFunction // When cursor for a previous page is used it's marked as reversed // This tells us to flip the descending flag on all used sort keys @@ -137,7 +137,7 @@ func (s Store) fetchFullPageOfApigwFunctions( hasNext bool ) - set = make([]*types.Function, 0, DefaultSliceCapacity) + set = make([]*types.ApigwFunction, 0, DefaultSliceCapacity) for try := 0; try < MaxRefetches; try++ { if cursor != nil { @@ -235,12 +235,12 @@ func (s Store) fetchFullPageOfApigwFunctions( func (s Store) QueryApigwFunctions( ctx context.Context, q squirrel.Sqlizer, - check func(*types.Function) (bool, error), -) ([]*types.Function, error) { + check func(*types.ApigwFunction) (bool, error), +) ([]*types.ApigwFunction, error) { var ( - tmp = make([]*types.Function, 0, DefaultSliceCapacity) - set = make([]*types.Function, 0, DefaultSliceCapacity) - res *types.Function + tmp = make([]*types.ApigwFunction, 0, DefaultSliceCapacity) + set = make([]*types.ApigwFunction, 0, DefaultSliceCapacity) + res *types.ApigwFunction // Query rows with rows, err = s.Query(ctx, q) @@ -272,21 +272,21 @@ func (s Store) QueryApigwFunctions( } // LookupApigwFunctionByID searches for function by ID -func (s Store) LookupApigwFunctionByID(ctx context.Context, id uint64) (*types.Function, error) { +func (s Store) LookupApigwFunctionByID(ctx context.Context, id uint64) (*types.ApigwFunction, error) { return s.execLookupApigwFunction(ctx, squirrel.Eq{ s.preprocessColumn("af.id", ""): store.PreprocessValue(id, ""), }) } // LookupApigwFunctionByRoute searches for function by route -func (s Store) LookupApigwFunctionByRoute(ctx context.Context, route string) (*types.Function, error) { +func (s Store) LookupApigwFunctionByRoute(ctx context.Context, route string) (*types.ApigwFunction, error) { return s.execLookupApigwFunction(ctx, squirrel.Eq{ s.preprocessColumn("af.rel_route", ""): store.PreprocessValue(route, ""), }) } // CreateApigwFunction creates one or more rows in apigw_functions table -func (s Store) CreateApigwFunction(ctx context.Context, rr ...*types.Function) (err error) { +func (s Store) CreateApigwFunction(ctx context.Context, rr ...*types.ApigwFunction) (err error) { for _, res := range rr { err = s.checkApigwFunctionConstraints(ctx, res) if err != nil { @@ -303,12 +303,12 @@ func (s Store) CreateApigwFunction(ctx context.Context, rr ...*types.Function) ( } // UpdateApigwFunction updates one or more existing rows in apigw_functions -func (s Store) UpdateApigwFunction(ctx context.Context, rr ...*types.Function) error { +func (s Store) UpdateApigwFunction(ctx context.Context, rr ...*types.ApigwFunction) error { return s.partialApigwFunctionUpdate(ctx, nil, rr...) } // partialApigwFunctionUpdate updates one or more existing rows in apigw_functions -func (s Store) partialApigwFunctionUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Function) (err error) { +func (s Store) partialApigwFunctionUpdate(ctx context.Context, onlyColumns []string, rr ...*types.ApigwFunction) (err error) { for _, res := range rr { err = s.checkApigwFunctionConstraints(ctx, res) if err != nil { @@ -330,7 +330,7 @@ func (s Store) partialApigwFunctionUpdate(ctx context.Context, onlyColumns []str } // DeleteApigwFunction Deletes one or more rows from apigw_functions table -func (s Store) DeleteApigwFunction(ctx context.Context, rr ...*types.Function) (err error) { +func (s Store) DeleteApigwFunction(ctx context.Context, rr ...*types.ApigwFunction) (err error) { for _, res := range rr { err = s.execDeleteApigwFunctions(ctx, squirrel.Eq{ @@ -357,8 +357,8 @@ func (s Store) TruncateApigwFunctions(ctx context.Context) error { } // execLookupApigwFunction prepares ApigwFunction query and executes it, -// returning types.Function (or error) -func (s Store) execLookupApigwFunction(ctx context.Context, cnd squirrel.Sqlizer) (res *types.Function, err error) { +// returning types.ApigwFunction (or error) +func (s Store) execLookupApigwFunction(ctx context.Context, cnd squirrel.Sqlizer) (res *types.ApigwFunction, err error) { var ( row rowScanner ) @@ -391,11 +391,11 @@ func (s Store) execDeleteApigwFunctions(ctx context.Context, cnd squirrel.Sqlize return s.Exec(ctx, s.DeleteBuilder(s.apigwFunctionTable("af")).Where(cnd)) } -func (s Store) internalApigwFunctionRowScanner(row rowScanner) (res *types.Function, err error) { - res = &types.Function{} +func (s Store) internalApigwFunctionRowScanner(row rowScanner) (res *types.ApigwFunction, err error) { + res = &types.ApigwFunction{} if _, has := s.config.RowScanners["apigwFunction"]; has { - scanner := s.config.RowScanners["apigwFunction"].(func(_ rowScanner, _ *types.Function) error) + scanner := s.config.RowScanners["apigwFunction"].(func(_ rowScanner, _ *types.ApigwFunction) error) err = scanner(row, res) } else { err = row.Scan( @@ -476,11 +476,11 @@ func (Store) sortableApigwFunctionColumns() map[string]string { } } -// internalApigwFunctionEncoder encodes fields from types.Function to store.Payload (map) +// internalApigwFunctionEncoder encodes fields from types.ApigwFunction to store.Payload (map) // // Encoding is done by using generic approach or by calling encodeApigwFunction // func when rdbms.customEncoder=true -func (s Store) internalApigwFunctionEncoder(res *types.Function) store.Payload { +func (s Store) internalApigwFunctionEncoder(res *types.ApigwFunction) store.Payload { return store.Payload{ "id": res.ID, "rel_route": res.Route, @@ -506,7 +506,7 @@ func (s Store) internalApigwFunctionEncoder(res *types.Function) store.Payload { // Known issue: // when collecting cursor values for query that sorts by unique column with partial index (ie: unique handle on // undeleted items) -func (s Store) collectApigwFunctionCursorValues(res *types.Function, cc ...*filter.SortExpr) *filter.PagingCursor { +func (s Store) collectApigwFunctionCursorValues(res *types.ApigwFunction, cc ...*filter.SortExpr) *filter.PagingCursor { var ( cursor = &filter.PagingCursor{LThen: filter.SortExprSet(cc).Reversed()} @@ -542,7 +542,7 @@ func (s Store) collectApigwFunctionCursorValues(res *types.Function, cc ...*filt // // Using built-in constraint checking would be more performant but unfortunately we cannot rely // on the full support (MySQL does not support conditional indexes) -func (s *Store) checkApigwFunctionConstraints(ctx context.Context, res *types.Function) error { +func (s *Store) checkApigwFunctionConstraints(ctx context.Context, res *types.ApigwFunction) error { // Consider resource valid when all fields in unique constraint check lookups // have valid (non-empty) value // diff --git a/store/rdbms/apigw_function.go b/store/rdbms/apigw_function.go index 80e2299c8..14e255023 100644 --- a/store/rdbms/apigw_function.go +++ b/store/rdbms/apigw_function.go @@ -6,8 +6,14 @@ import ( "github.com/cortezaproject/corteza-server/system/types" ) -func (s Store) convertApigwFunctionFilter(f types.FunctionFilter) (query squirrel.SelectBuilder, err error) { +func (s Store) convertApigwFunctionFilter(f types.ApigwFunctionFilter) (query squirrel.SelectBuilder, err error) { query = s.apigwFunctionsSelectBuilder() + query = filter.StateCondition(query, "af.deleted_at", f.Deleted) + + if f.RouteID > 0 { + query = query.Where(squirrel.Eq{"af.rel_route": f.RouteID}) + } + return } diff --git a/store/rdbms/apigw_route.gen.go b/store/rdbms/apigw_route.gen.go index 3fc10724e..44e2a82ec 100644 --- a/store/rdbms/apigw_route.gen.go +++ b/store/rdbms/apigw_route.gen.go @@ -24,11 +24,11 @@ var _ = errors.Is // SearchApigwRoutes returns all matching rows // // This function calls convertApigwRouteFilter with the given -// types.RouteFilter and expects to receive a working squirrel.SelectBuilder -func (s Store) SearchApigwRoutes(ctx context.Context, f types.RouteFilter) (types.RouteSet, types.RouteFilter, error) { +// types.ApigwRouteFilter and expects to receive a working squirrel.SelectBuilder +func (s Store) SearchApigwRoutes(ctx context.Context, f types.ApigwRouteFilter) (types.ApigwRouteSet, types.ApigwRouteFilter, error) { var ( err error - set []*types.Route + set []*types.ApigwRoute q squirrel.SelectBuilder ) @@ -112,11 +112,11 @@ func (s Store) fetchFullPageOfApigwRoutes( sort filter.SortExprSet, cursor *filter.PagingCursor, reqItems uint, - check func(*types.Route) (bool, error), + check func(*types.ApigwRoute) (bool, error), cursorCond func(*filter.PagingCursor) squirrel.Sqlizer, -) (set []*types.Route, prev, next *filter.PagingCursor, err error) { +) (set []*types.ApigwRoute, prev, next *filter.PagingCursor, err error) { var ( - aux []*types.Route + aux []*types.ApigwRoute // When cursor for a previous page is used it's marked as reversed // This tells us to flip the descending flag on all used sort keys @@ -137,7 +137,7 @@ func (s Store) fetchFullPageOfApigwRoutes( hasNext bool ) - set = make([]*types.Route, 0, DefaultSliceCapacity) + set = make([]*types.ApigwRoute, 0, DefaultSliceCapacity) for try := 0; try < MaxRefetches; try++ { if cursor != nil { @@ -235,12 +235,12 @@ func (s Store) fetchFullPageOfApigwRoutes( func (s Store) QueryApigwRoutes( ctx context.Context, q squirrel.Sqlizer, - check func(*types.Route) (bool, error), -) ([]*types.Route, error) { + check func(*types.ApigwRoute) (bool, error), +) ([]*types.ApigwRoute, error) { var ( - tmp = make([]*types.Route, 0, DefaultSliceCapacity) - set = make([]*types.Route, 0, DefaultSliceCapacity) - res *types.Route + tmp = make([]*types.ApigwRoute, 0, DefaultSliceCapacity) + set = make([]*types.ApigwRoute, 0, DefaultSliceCapacity) + res *types.ApigwRoute // Query rows with rows, err = s.Query(ctx, q) @@ -272,21 +272,21 @@ func (s Store) QueryApigwRoutes( } // LookupApigwRouteByID searches for route by ID -func (s Store) LookupApigwRouteByID(ctx context.Context, id uint64) (*types.Route, error) { +func (s Store) LookupApigwRouteByID(ctx context.Context, id uint64) (*types.ApigwRoute, error) { return s.execLookupApigwRoute(ctx, squirrel.Eq{ s.preprocessColumn("ar.id", ""): store.PreprocessValue(id, ""), }) } // LookupApigwRouteByEndpoint searches for route by endpoint -func (s Store) LookupApigwRouteByEndpoint(ctx context.Context, endpoint string) (*types.Route, error) { +func (s Store) LookupApigwRouteByEndpoint(ctx context.Context, endpoint string) (*types.ApigwRoute, error) { return s.execLookupApigwRoute(ctx, squirrel.Eq{ s.preprocessColumn("ar.endpoint", ""): store.PreprocessValue(endpoint, ""), }) } // CreateApigwRoute creates one or more rows in apigw_routes table -func (s Store) CreateApigwRoute(ctx context.Context, rr ...*types.Route) (err error) { +func (s Store) CreateApigwRoute(ctx context.Context, rr ...*types.ApigwRoute) (err error) { for _, res := range rr { err = s.checkApigwRouteConstraints(ctx, res) if err != nil { @@ -303,12 +303,12 @@ func (s Store) CreateApigwRoute(ctx context.Context, rr ...*types.Route) (err er } // UpdateApigwRoute updates one or more existing rows in apigw_routes -func (s Store) UpdateApigwRoute(ctx context.Context, rr ...*types.Route) error { +func (s Store) UpdateApigwRoute(ctx context.Context, rr ...*types.ApigwRoute) error { return s.partialApigwRouteUpdate(ctx, nil, rr...) } // partialApigwRouteUpdate updates one or more existing rows in apigw_routes -func (s Store) partialApigwRouteUpdate(ctx context.Context, onlyColumns []string, rr ...*types.Route) (err error) { +func (s Store) partialApigwRouteUpdate(ctx context.Context, onlyColumns []string, rr ...*types.ApigwRoute) (err error) { for _, res := range rr { err = s.checkApigwRouteConstraints(ctx, res) if err != nil { @@ -330,7 +330,7 @@ func (s Store) partialApigwRouteUpdate(ctx context.Context, onlyColumns []string } // DeleteApigwRoute Deletes one or more rows from apigw_routes table -func (s Store) DeleteApigwRoute(ctx context.Context, rr ...*types.Route) (err error) { +func (s Store) DeleteApigwRoute(ctx context.Context, rr ...*types.ApigwRoute) (err error) { for _, res := range rr { err = s.execDeleteApigwRoutes(ctx, squirrel.Eq{ @@ -357,8 +357,8 @@ func (s Store) TruncateApigwRoutes(ctx context.Context) error { } // execLookupApigwRoute prepares ApigwRoute query and executes it, -// returning types.Route (or error) -func (s Store) execLookupApigwRoute(ctx context.Context, cnd squirrel.Sqlizer) (res *types.Route, err error) { +// returning types.ApigwRoute (or error) +func (s Store) execLookupApigwRoute(ctx context.Context, cnd squirrel.Sqlizer) (res *types.ApigwRoute, err error) { var ( row rowScanner ) @@ -391,11 +391,11 @@ func (s Store) execDeleteApigwRoutes(ctx context.Context, cnd squirrel.Sqlizer) return s.Exec(ctx, s.DeleteBuilder(s.apigwRouteTable("ar")).Where(cnd)) } -func (s Store) internalApigwRouteRowScanner(row rowScanner) (res *types.Route, err error) { - res = &types.Route{} +func (s Store) internalApigwRouteRowScanner(row rowScanner) (res *types.ApigwRoute, err error) { + res = &types.ApigwRoute{} if _, has := s.config.RowScanners["apigwRoute"]; has { - scanner := s.config.RowScanners["apigwRoute"].(func(_ rowScanner, _ *types.Route) error) + scanner := s.config.RowScanners["apigwRoute"].(func(_ rowScanner, _ *types.ApigwRoute) error) err = scanner(row, res) } else { err = row.Scan( @@ -476,11 +476,11 @@ func (Store) sortableApigwRouteColumns() map[string]string { } } -// internalApigwRouteEncoder encodes fields from types.Route to store.Payload (map) +// internalApigwRouteEncoder encodes fields from types.ApigwRoute to store.Payload (map) // // Encoding is done by using generic approach or by calling encodeApigwRoute // func when rdbms.customEncoder=true -func (s Store) internalApigwRouteEncoder(res *types.Route) store.Payload { +func (s Store) internalApigwRouteEncoder(res *types.ApigwRoute) store.Payload { return store.Payload{ "id": res.ID, "endpoint": res.Endpoint, @@ -506,7 +506,7 @@ func (s Store) internalApigwRouteEncoder(res *types.Route) store.Payload { // Known issue: // when collecting cursor values for query that sorts by unique column with partial index (ie: unique handle on // undeleted items) -func (s Store) collectApigwRouteCursorValues(res *types.Route, cc ...*filter.SortExpr) *filter.PagingCursor { +func (s Store) collectApigwRouteCursorValues(res *types.ApigwRoute, cc ...*filter.SortExpr) *filter.PagingCursor { var ( cursor = &filter.PagingCursor{LThen: filter.SortExprSet(cc).Reversed()} @@ -542,7 +542,7 @@ func (s Store) collectApigwRouteCursorValues(res *types.Route, cc ...*filter.Sor // // Using built-in constraint checking would be more performant but unfortunately we cannot rely // on the full support (MySQL does not support conditional indexes) -func (s *Store) checkApigwRouteConstraints(ctx context.Context, res *types.Route) error { +func (s *Store) checkApigwRouteConstraints(ctx context.Context, res *types.ApigwRoute) error { // Consider resource valid when all fields in unique constraint check lookups // have valid (non-empty) value // diff --git a/store/rdbms/apigw_route.go b/store/rdbms/apigw_route.go index 2de3533a7..5086b15ec 100644 --- a/store/rdbms/apigw_route.go +++ b/store/rdbms/apigw_route.go @@ -6,7 +6,7 @@ import ( "github.com/cortezaproject/corteza-server/system/types" ) -func (s Store) convertApigwRouteFilter(f types.RouteFilter) (query squirrel.SelectBuilder, err error) { +func (s Store) convertApigwRouteFilter(f types.ApigwRouteFilter) (query squirrel.SelectBuilder, err error) { query = s.apigwRoutesSelectBuilder() query = filter.StateCondition(query, "ar.deleted_at", f.Deleted) return diff --git a/system/rest.yaml b/system/rest.yaml index ee0b94405..fb6bd5b09 100644 --- a/system/rest.yaml +++ b/system/rest.yaml @@ -1507,7 +1507,7 @@ endpoints: - title: API Gateway routes path: "/apigw/route" - entrypoint: route + entrypoint: apigwRoute authentication: [] imports: - github.com/cortezaproject/corteza-server/pkg/label @@ -1518,7 +1518,7 @@ endpoints: path: "/" parameters: get: - - { name: routeID, type: "[]string", title: "Filter by route ID" } + - { name: routeID, type: "[]uint64", title: "Filter by route ID" } - { name: query, type: "string", title: "Filter routes" } - { name: deleted, type: "uint64", title: "Exclude (0, default), include (1) or return only (2) deleted routes" } - { name: disabled, type: "uint64", title: "Exclude (0, default), include (1) or return only (2) disabled routes" } @@ -1579,7 +1579,7 @@ endpoints: - title: API Gateway functions path: "/apigw/function" - entrypoint: function + entrypoint: apigwFunction authentication: [] imports: - github.com/cortezaproject/corteza-server/system/types @@ -1590,8 +1590,7 @@ endpoints: path: "/" parameters: get: - - { name: functionID, type: "[]string", title: "Filter by function ID" } - - { name: routeID, type: "string", title: "Filter by route ID" } + - { name: routeID, type: "uint64", title: "Filter by route ID" } - { name: query, type: "string", title: "Filter functions" } - { name: deleted, type: "uint64", title: "Exclude (0, default), include (1) or return only (2) deleted functions" } - { name: disabled, type: "uint64", title: "Exclude (0, default), include (1) or return only (2) disabled functions" } @@ -1604,11 +1603,11 @@ endpoints: path: "" parameters: post: - - { name: routeID, type: uint64, title: "Route", required: true } - - { name: weight, type: uint64, title: "Function priority" } - - { name: kind, type: "types.ApigwFunctionKind", title: "Function kind" } - - { name: ref, type: string, title: "Function ref" } - - { name: params, type: "types.FuncParams", title: "Function parameters", parser: "types.ParseApigwfFunctionParams" } + - { name: routeID, type: uint64, title: "Route", required: true } + - { name: weight, type: uint64, title: "Function priority" } + - { name: kind, type: string, title: "Function kind" } + - { name: ref, type: string, title: "Function ref" } + - { name: params, type: "types.ApigwFuncParams", title: "Function parameters", parser: "types.ParseApigwfFunctionParams" } - name: update method: POST title: Update route details @@ -1616,11 +1615,11 @@ endpoints: parameters: path: [ { name: functionID, type: uint64, required: true, title: "Function ID" } ] post: - - { name: routeID, type: uint64, title: "Route", required: true } - - { name: weight, type: uint64, title: "Function priority" } - - { name: kind, type: "types.ApigwFunctionKind", title: "Function kind" } - - { name: ref, type: string, title: "Function ref" } - - { name: params, type: "types.FuncParams", title: "Function parameters", parser: "types.ParseApigwfFunctionParams" } + - { name: routeID, type: uint64, title: "Route", required: true } + - { name: weight, type: uint64, title: "Function priority" } + - { name: kind, type: string, title: "Function kind" } + - { name: ref, type: string, title: "Function ref" } + - { name: params, type: "types.ApigwFuncParams", title: "Function parameters", parser: "types.ParseApigwfFunctionParams" } - name: read method: GET title: Read function details diff --git a/system/rest/function.go b/system/rest/apigw_function.go similarity index 52% rename from system/rest/function.go rename to system/rest/apigw_function.go index 5fd0d24c1..a3a4946dc 100644 --- a/system/rest/function.go +++ b/system/rest/apigw_function.go @@ -11,42 +11,43 @@ import ( ) type ( - Function struct { + ApigwFunction struct { svc functionService ac templateAccessController } functionPayload struct { - *types.Function + *types.ApigwFunction } functionSetPayload struct { - Filter types.FunctionFilter `json:"filter"` - Set []*functionPayload `json:"set"` + Filter types.ApigwFunctionFilter `json:"filter"` + Set []*functionPayload `json:"set"` } functionService interface { - FindByID(ctx context.Context, ID uint64) (*types.Function, error) - Search(ctx context.Context, filter types.FunctionFilter) (types.FunctionSet, types.FunctionFilter, error) - Create(ctx context.Context, new *types.Function) (*types.Function, error) - Update(ctx context.Context, upd *types.Function) (*types.Function, error) + FindByID(ctx context.Context, ID uint64) (*types.ApigwFunction, error) + Search(ctx context.Context, filter types.ApigwFunctionFilter) (types.ApigwFunctionSet, types.ApigwFunctionFilter, error) + Create(ctx context.Context, new *types.ApigwFunction) (*types.ApigwFunction, error) + Update(ctx context.Context, upd *types.ApigwFunction) (*types.ApigwFunction, error) DeleteByID(ctx context.Context, ID uint64) error UndeleteByID(ctx context.Context, ID uint64) error Definitions(context.Context, string) (interface{}, error) } ) -func (Function) New() *Function { - return &Function{ +func (ApigwFunction) New() *ApigwFunction { + return &ApigwFunction{ svc: service.DefaultFunction, ac: service.DefaultAccessControl, } } -func (ctrl *Function) List(ctx context.Context, r *request.FunctionList) (interface{}, error) { +func (ctrl *ApigwFunction) List(ctx context.Context, r *request.ApigwFunctionList) (interface{}, error) { var ( err error - f = types.FunctionFilter{ + f = types.ApigwFunctionFilter{ + RouteID: r.RouteID, Deleted: filter.State(r.Deleted), } ) @@ -64,13 +65,13 @@ func (ctrl *Function) List(ctx context.Context, r *request.FunctionList) (interf return ctrl.makeFilterPayload(ctx, set, filter, err) } -func (ctrl *Function) Create(ctx context.Context, r *request.FunctionCreate) (interface{}, error) { +func (ctrl *ApigwFunction) Create(ctx context.Context, r *request.ApigwFunctionCreate) (interface{}, error) { var ( err error - q = &types.Function{ + q = &types.ApigwFunction{ Route: r.RouteID, Weight: r.Weight, - Kind: string(r.Kind), + Kind: r.Kind, Ref: r.Ref, Params: r.Params, } @@ -81,18 +82,18 @@ func (ctrl *Function) Create(ctx context.Context, r *request.FunctionCreate) (in return ctrl.makePayload(ctx, q, err) } -func (ctrl *Function) Read(ctx context.Context, r *request.FunctionRead) (interface{}, error) { +func (ctrl *ApigwFunction) Read(ctx context.Context, r *request.ApigwFunctionRead) (interface{}, error) { return ctrl.svc.FindByID(ctx, r.FunctionID) } -func (ctrl *Function) Update(ctx context.Context, r *request.FunctionUpdate) (interface{}, error) { +func (ctrl *ApigwFunction) Update(ctx context.Context, r *request.ApigwFunctionUpdate) (interface{}, error) { var ( err error - q = &types.Function{ + q = &types.ApigwFunction{ ID: r.FunctionID, Route: r.RouteID, Weight: r.Weight, - Kind: string(r.Kind), + Kind: r.Kind, Ref: r.Ref, Params: r.Params, } @@ -103,31 +104,31 @@ func (ctrl *Function) Update(ctx context.Context, r *request.FunctionUpdate) (in return ctrl.makePayload(ctx, q, err) } -func (ctrl *Function) Delete(ctx context.Context, r *request.FunctionDelete) (interface{}, error) { +func (ctrl *ApigwFunction) Delete(ctx context.Context, r *request.ApigwFunctionDelete) (interface{}, error) { return api.OK(), ctrl.svc.DeleteByID(ctx, r.FunctionID) } -func (ctrl *Function) Definitions(ctx context.Context, r *request.FunctionDefinitions) (interface{}, error) { +func (ctrl *ApigwFunction) Definitions(ctx context.Context, r *request.ApigwFunctionDefinitions) (interface{}, error) { return ctrl.svc.Definitions(ctx, r.Kind) } -func (ctrl *Function) Undelete(ctx context.Context, r *request.FunctionUndelete) (interface{}, error) { +func (ctrl *ApigwFunction) Undelete(ctx context.Context, r *request.ApigwFunctionUndelete) (interface{}, error) { return api.OK(), ctrl.svc.UndeleteByID(ctx, r.FunctionID) } -func (ctrl *Function) makePayload(ctx context.Context, q *types.Function, err error) (*functionPayload, error) { +func (ctrl *ApigwFunction) makePayload(ctx context.Context, q *types.ApigwFunction, err error) (*functionPayload, error) { if err != nil || q == nil { return nil, err } qq := &functionPayload{ - Function: q, + ApigwFunction: q, } return qq, nil } -func (ctrl *Function) makeFilterPayload(ctx context.Context, nn types.FunctionSet, f types.FunctionFilter, err error) (*functionSetPayload, error) { +func (ctrl *ApigwFunction) makeFilterPayload(ctx context.Context, nn types.ApigwFunctionSet, f types.ApigwFunctionFilter, err error) (*functionSetPayload, error) { if err != nil { return nil, err } diff --git a/system/rest/route.go b/system/rest/apigw_route.go similarity index 55% rename from system/rest/route.go rename to system/rest/apigw_route.go index fb88c22bf..d688cc986 100644 --- a/system/rest/route.go +++ b/system/rest/apigw_route.go @@ -11,41 +11,41 @@ import ( ) type ( - Route struct { + ApigwRoute struct { svc routeService ac templateAccessController } routePayload struct { - *types.Route + *types.ApigwRoute } routeSetPayload struct { - Filter types.RouteFilter `json:"filter"` - Set []*routePayload `json:"set"` + Filter types.ApigwRouteFilter `json:"filter"` + Set []*routePayload `json:"set"` } routeService interface { - FindByID(ctx context.Context, ID uint64) (*types.Route, error) - Create(ctx context.Context, new *types.Route) (*types.Route, error) - Update(ctx context.Context, upd *types.Route) (*types.Route, error) + FindByID(ctx context.Context, ID uint64) (*types.ApigwRoute, error) + Create(ctx context.Context, new *types.ApigwRoute) (*types.ApigwRoute, error) + Update(ctx context.Context, upd *types.ApigwRoute) (*types.ApigwRoute, error) DeleteByID(ctx context.Context, ID uint64) error UndeleteByID(ctx context.Context, ID uint64) error - Search(ctx context.Context, filter types.RouteFilter) (types.RouteSet, types.RouteFilter, error) + Search(ctx context.Context, filter types.ApigwRouteFilter) (types.ApigwRouteSet, types.ApigwRouteFilter, error) } ) -func (Route) New() *Route { - return &Route{ +func (ApigwRoute) New() *ApigwRoute { + return &ApigwRoute{ svc: service.DefaultRoute, ac: service.DefaultAccessControl, } } -func (ctrl *Route) List(ctx context.Context, r *request.RouteList) (interface{}, error) { +func (ctrl *ApigwRoute) List(ctx context.Context, r *request.ApigwRouteList) (interface{}, error) { var ( err error - f = types.RouteFilter{ + f = types.ApigwRouteFilter{ Deleted: filter.State(r.Deleted), } ) @@ -63,10 +63,10 @@ func (ctrl *Route) List(ctx context.Context, r *request.RouteList) (interface{}, return ctrl.makeFilterPayload(ctx, set, filter, err) } -func (ctrl *Route) Create(ctx context.Context, r *request.RouteCreate) (interface{}, error) { +func (ctrl *ApigwRoute) Create(ctx context.Context, r *request.ApigwRouteCreate) (interface{}, error) { var ( err error - q = &types.Route{ + q = &types.ApigwRoute{ Endpoint: r.Endpoint, Method: r.Method, Debug: r.Debug, @@ -79,14 +79,14 @@ func (ctrl *Route) Create(ctx context.Context, r *request.RouteCreate) (interfac return ctrl.makePayload(ctx, q, err) } -func (ctrl *Route) Read(ctx context.Context, r *request.RouteRead) (interface{}, error) { +func (ctrl *ApigwRoute) Read(ctx context.Context, r *request.ApigwRouteRead) (interface{}, error) { return ctrl.svc.FindByID(ctx, r.RouteID) } -func (ctrl *Route) Update(ctx context.Context, r *request.RouteUpdate) (interface{}, error) { +func (ctrl *ApigwRoute) Update(ctx context.Context, r *request.ApigwRouteUpdate) (interface{}, error) { var ( err error - q = &types.Route{ + q = &types.ApigwRoute{ ID: r.RouteID, Endpoint: r.Endpoint, Method: r.Method, @@ -101,27 +101,27 @@ func (ctrl *Route) Update(ctx context.Context, r *request.RouteUpdate) (interfac return ctrl.makePayload(ctx, q, err) } -func (ctrl *Route) Delete(ctx context.Context, r *request.RouteDelete) (interface{}, error) { +func (ctrl *ApigwRoute) Delete(ctx context.Context, r *request.ApigwRouteDelete) (interface{}, error) { return api.OK(), ctrl.svc.DeleteByID(ctx, r.RouteID) } -func (ctrl *Route) Undelete(ctx context.Context, r *request.RouteUndelete) (interface{}, error) { +func (ctrl *ApigwRoute) Undelete(ctx context.Context, r *request.ApigwRouteUndelete) (interface{}, error) { return api.OK(), ctrl.svc.UndeleteByID(ctx, r.RouteID) } -func (ctrl *Route) makePayload(ctx context.Context, q *types.Route, err error) (*routePayload, error) { +func (ctrl *ApigwRoute) makePayload(ctx context.Context, q *types.ApigwRoute, err error) (*routePayload, error) { if err != nil || q == nil { return nil, err } qq := &routePayload{ - Route: q, + ApigwRoute: q, } return qq, nil } -func (ctrl *Route) makeFilterPayload(ctx context.Context, nn types.RouteSet, f types.RouteFilter, err error) (*routeSetPayload, error) { +func (ctrl *ApigwRoute) makeFilterPayload(ctx context.Context, nn types.ApigwRouteSet, f types.ApigwRouteFilter, err error) (*routeSetPayload, error) { if err != nil { return nil, err } diff --git a/system/rest/handlers/function.go b/system/rest/handlers/apigwFunction.go similarity index 75% rename from system/rest/handlers/function.go rename to system/rest/handlers/apigwFunction.go index 464a76e45..fc583c45c 100644 --- a/system/rest/handlers/function.go +++ b/system/rest/handlers/apigwFunction.go @@ -18,18 +18,18 @@ import ( type ( // Internal API interface - FunctionAPI interface { - List(context.Context, *request.FunctionList) (interface{}, error) - Create(context.Context, *request.FunctionCreate) (interface{}, error) - Update(context.Context, *request.FunctionUpdate) (interface{}, error) - Read(context.Context, *request.FunctionRead) (interface{}, error) - Delete(context.Context, *request.FunctionDelete) (interface{}, error) - Undelete(context.Context, *request.FunctionUndelete) (interface{}, error) - Definitions(context.Context, *request.FunctionDefinitions) (interface{}, error) + ApigwFunctionAPI interface { + List(context.Context, *request.ApigwFunctionList) (interface{}, error) + Create(context.Context, *request.ApigwFunctionCreate) (interface{}, error) + Update(context.Context, *request.ApigwFunctionUpdate) (interface{}, error) + Read(context.Context, *request.ApigwFunctionRead) (interface{}, error) + Delete(context.Context, *request.ApigwFunctionDelete) (interface{}, error) + Undelete(context.Context, *request.ApigwFunctionUndelete) (interface{}, error) + Definitions(context.Context, *request.ApigwFunctionDefinitions) (interface{}, error) } // HTTP API interface - Function struct { + ApigwFunction struct { List func(http.ResponseWriter, *http.Request) Create func(http.ResponseWriter, *http.Request) Update func(http.ResponseWriter, *http.Request) @@ -40,11 +40,11 @@ type ( } ) -func NewFunction(h FunctionAPI) *Function { - return &Function{ +func NewApigwFunction(h ApigwFunctionAPI) *ApigwFunction { + return &ApigwFunction{ List: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewFunctionList() + params := request.NewApigwFunctionList() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -60,7 +60,7 @@ func NewFunction(h FunctionAPI) *Function { }, Create: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewFunctionCreate() + params := request.NewApigwFunctionCreate() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -76,7 +76,7 @@ func NewFunction(h FunctionAPI) *Function { }, Update: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewFunctionUpdate() + params := request.NewApigwFunctionUpdate() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -92,7 +92,7 @@ func NewFunction(h FunctionAPI) *Function { }, Read: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewFunctionRead() + params := request.NewApigwFunctionRead() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -108,7 +108,7 @@ func NewFunction(h FunctionAPI) *Function { }, Delete: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewFunctionDelete() + params := request.NewApigwFunctionDelete() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -124,7 +124,7 @@ func NewFunction(h FunctionAPI) *Function { }, Undelete: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewFunctionUndelete() + params := request.NewApigwFunctionUndelete() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -140,7 +140,7 @@ func NewFunction(h FunctionAPI) *Function { }, Definitions: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewFunctionDefinitions() + params := request.NewApigwFunctionDefinitions() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -157,7 +157,7 @@ func NewFunction(h FunctionAPI) *Function { } } -func (h Function) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { +func (h ApigwFunction) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { r.Group(func(r chi.Router) { r.Use(middlewares...) r.Get("/apigw/function/", h.List) diff --git a/system/rest/handlers/route.go b/system/rest/handlers/apigwRoute.go similarity index 76% rename from system/rest/handlers/route.go rename to system/rest/handlers/apigwRoute.go index da7dcab82..d34587a4d 100644 --- a/system/rest/handlers/route.go +++ b/system/rest/handlers/apigwRoute.go @@ -18,17 +18,17 @@ import ( type ( // Internal API interface - RouteAPI interface { - List(context.Context, *request.RouteList) (interface{}, error) - Create(context.Context, *request.RouteCreate) (interface{}, error) - Update(context.Context, *request.RouteUpdate) (interface{}, error) - Read(context.Context, *request.RouteRead) (interface{}, error) - Delete(context.Context, *request.RouteDelete) (interface{}, error) - Undelete(context.Context, *request.RouteUndelete) (interface{}, error) + ApigwRouteAPI interface { + List(context.Context, *request.ApigwRouteList) (interface{}, error) + Create(context.Context, *request.ApigwRouteCreate) (interface{}, error) + Update(context.Context, *request.ApigwRouteUpdate) (interface{}, error) + Read(context.Context, *request.ApigwRouteRead) (interface{}, error) + Delete(context.Context, *request.ApigwRouteDelete) (interface{}, error) + Undelete(context.Context, *request.ApigwRouteUndelete) (interface{}, error) } // HTTP API interface - Route struct { + ApigwRoute struct { List func(http.ResponseWriter, *http.Request) Create func(http.ResponseWriter, *http.Request) Update func(http.ResponseWriter, *http.Request) @@ -38,11 +38,11 @@ type ( } ) -func NewRoute(h RouteAPI) *Route { - return &Route{ +func NewApigwRoute(h ApigwRouteAPI) *ApigwRoute { + return &ApigwRoute{ List: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewRouteList() + params := request.NewApigwRouteList() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -58,7 +58,7 @@ func NewRoute(h RouteAPI) *Route { }, Create: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewRouteCreate() + params := request.NewApigwRouteCreate() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -74,7 +74,7 @@ func NewRoute(h RouteAPI) *Route { }, Update: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewRouteUpdate() + params := request.NewApigwRouteUpdate() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -90,7 +90,7 @@ func NewRoute(h RouteAPI) *Route { }, Read: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewRouteRead() + params := request.NewApigwRouteRead() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -106,7 +106,7 @@ func NewRoute(h RouteAPI) *Route { }, Delete: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewRouteDelete() + params := request.NewApigwRouteDelete() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -122,7 +122,7 @@ func NewRoute(h RouteAPI) *Route { }, Undelete: func(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - params := request.NewRouteUndelete() + params := request.NewApigwRouteUndelete() if err := params.Fill(r); err != nil { api.Send(w, r, err) return @@ -139,7 +139,7 @@ func NewRoute(h RouteAPI) *Route { } } -func (h Route) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { +func (h ApigwRoute) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) { r.Group(func(r chi.Router) { r.Use(middlewares...) r.Get("/apigw/route/", h.List) diff --git a/system/rest/request/function.go b/system/rest/request/apigwFunction.go similarity index 73% rename from system/rest/request/function.go rename to system/rest/request/apigwFunction.go index 20c4a4ea4..40d697951 100644 --- a/system/rest/request/function.go +++ b/system/rest/request/apigwFunction.go @@ -34,16 +34,11 @@ var ( type ( // Internal API interface - FunctionList struct { - // FunctionID GET parameter - // - // Filter by function ID - FunctionID []string - + ApigwFunctionList struct { // RouteID GET parameter // // Filter by route ID - RouteID string + RouteID uint64 `json:",string"` // Query GET parameter // @@ -76,7 +71,7 @@ type ( Sort string } - FunctionCreate struct { + ApigwFunctionCreate struct { // RouteID POST parameter // // Route @@ -90,7 +85,7 @@ type ( // Kind POST parameter // // Function kind - Kind types.ApigwFunctionKind + Kind string // Ref POST parameter // @@ -100,10 +95,10 @@ type ( // Params POST parameter // // Function parameters - Params types.FuncParams + Params types.ApigwFuncParams } - FunctionUpdate struct { + ApigwFunctionUpdate struct { // FunctionID PATH parameter // // Function ID @@ -122,7 +117,7 @@ type ( // Kind POST parameter // // Function kind - Kind types.ApigwFunctionKind + Kind string // Ref POST parameter // @@ -132,31 +127,31 @@ type ( // Params POST parameter // // Function parameters - Params types.FuncParams + Params types.ApigwFuncParams } - FunctionRead struct { + ApigwFunctionRead struct { // FunctionID PATH parameter // // Function ID FunctionID uint64 `json:",string"` } - FunctionDelete struct { + ApigwFunctionDelete struct { // FunctionID PATH parameter // // Function ID FunctionID uint64 `json:",string"` } - FunctionUndelete struct { + ApigwFunctionUndelete struct { // FunctionID PATH parameter // // Function ID FunctionID uint64 `json:",string"` } - FunctionDefinitions struct { + ApigwFunctionDefinitions struct { // Kind GET parameter // // Filter functions by kind @@ -164,15 +159,14 @@ type ( } ) -// NewFunctionList request -func NewFunctionList() *FunctionList { - return &FunctionList{} +// NewApigwFunctionList request +func NewApigwFunctionList() *ApigwFunctionList { + return &ApigwFunctionList{} } // Auditable returns all auditable/loggable parameters -func (r FunctionList) Auditable() map[string]interface{} { +func (r ApigwFunctionList) Auditable() map[string]interface{} { return map[string]interface{}{ - "functionID": r.FunctionID, "routeID": r.RouteID, "query": r.Query, "deleted": r.Deleted, @@ -184,65 +178,49 @@ func (r FunctionList) Auditable() map[string]interface{} { } // Auditable returns all auditable/loggable parameters -func (r FunctionList) GetFunctionID() []string { - return r.FunctionID -} - -// Auditable returns all auditable/loggable parameters -func (r FunctionList) GetRouteID() string { +func (r ApigwFunctionList) GetRouteID() uint64 { return r.RouteID } // Auditable returns all auditable/loggable parameters -func (r FunctionList) GetQuery() string { +func (r ApigwFunctionList) GetQuery() string { return r.Query } // Auditable returns all auditable/loggable parameters -func (r FunctionList) GetDeleted() uint64 { +func (r ApigwFunctionList) GetDeleted() uint64 { return r.Deleted } // Auditable returns all auditable/loggable parameters -func (r FunctionList) GetDisabled() uint64 { +func (r ApigwFunctionList) GetDisabled() uint64 { return r.Disabled } // Auditable returns all auditable/loggable parameters -func (r FunctionList) GetLimit() uint { +func (r ApigwFunctionList) GetLimit() uint { return r.Limit } // Auditable returns all auditable/loggable parameters -func (r FunctionList) GetPageCursor() string { +func (r ApigwFunctionList) GetPageCursor() string { return r.PageCursor } // Auditable returns all auditable/loggable parameters -func (r FunctionList) GetSort() string { +func (r ApigwFunctionList) GetSort() string { return r.Sort } // Fill processes request and fills internal variables -func (r *FunctionList) Fill(req *http.Request) (err error) { +func (r *ApigwFunctionList) Fill(req *http.Request) (err error) { { // GET params tmp := req.URL.Query() - if val, ok := tmp["functionID[]"]; ok { - r.FunctionID, err = val, nil - if err != nil { - return err - } - } else if val, ok := tmp["functionID"]; ok { - r.FunctionID, err = val, nil - if err != nil { - return err - } - } if val, ok := tmp["routeID"]; ok && len(val) > 0 { - r.RouteID, err = val[0], nil + r.RouteID, err = payload.ParseUint64(val[0]), nil if err != nil { return err } @@ -288,13 +266,13 @@ func (r *FunctionList) Fill(req *http.Request) (err error) { return err } -// NewFunctionCreate request -func NewFunctionCreate() *FunctionCreate { - return &FunctionCreate{} +// NewApigwFunctionCreate request +func NewApigwFunctionCreate() *ApigwFunctionCreate { + return &ApigwFunctionCreate{} } // Auditable returns all auditable/loggable parameters -func (r FunctionCreate) Auditable() map[string]interface{} { +func (r ApigwFunctionCreate) Auditable() map[string]interface{} { return map[string]interface{}{ "routeID": r.RouteID, "weight": r.Weight, @@ -305,32 +283,32 @@ func (r FunctionCreate) Auditable() map[string]interface{} { } // Auditable returns all auditable/loggable parameters -func (r FunctionCreate) GetRouteID() uint64 { +func (r ApigwFunctionCreate) GetRouteID() uint64 { return r.RouteID } // Auditable returns all auditable/loggable parameters -func (r FunctionCreate) GetWeight() uint64 { +func (r ApigwFunctionCreate) GetWeight() uint64 { return r.Weight } // Auditable returns all auditable/loggable parameters -func (r FunctionCreate) GetKind() types.ApigwFunctionKind { +func (r ApigwFunctionCreate) GetKind() string { return r.Kind } // Auditable returns all auditable/loggable parameters -func (r FunctionCreate) GetRef() string { +func (r ApigwFunctionCreate) GetRef() string { return r.Ref } // Auditable returns all auditable/loggable parameters -func (r FunctionCreate) GetParams() types.FuncParams { +func (r ApigwFunctionCreate) GetParams() types.ApigwFuncParams { return r.Params } // Fill processes request and fills internal variables -func (r *FunctionCreate) Fill(req *http.Request) (err error) { +func (r *ApigwFunctionCreate) Fill(req *http.Request) (err error) { if strings.ToLower(req.Header.Get("content-type")) == "application/json" { err = json.NewDecoder(req.Body).Decode(r) @@ -365,7 +343,7 @@ func (r *FunctionCreate) Fill(req *http.Request) (err error) { } if val, ok := req.Form["kind"]; ok && len(val) > 0 { - r.Kind, err = types.ApigwFunctionKind(val[0]), nil + r.Kind, err = val[0], nil if err != nil { return err } @@ -394,13 +372,13 @@ func (r *FunctionCreate) Fill(req *http.Request) (err error) { return err } -// NewFunctionUpdate request -func NewFunctionUpdate() *FunctionUpdate { - return &FunctionUpdate{} +// NewApigwFunctionUpdate request +func NewApigwFunctionUpdate() *ApigwFunctionUpdate { + return &ApigwFunctionUpdate{} } // Auditable returns all auditable/loggable parameters -func (r FunctionUpdate) Auditable() map[string]interface{} { +func (r ApigwFunctionUpdate) Auditable() map[string]interface{} { return map[string]interface{}{ "functionID": r.FunctionID, "routeID": r.RouteID, @@ -412,37 +390,37 @@ func (r FunctionUpdate) Auditable() map[string]interface{} { } // Auditable returns all auditable/loggable parameters -func (r FunctionUpdate) GetFunctionID() uint64 { +func (r ApigwFunctionUpdate) GetFunctionID() uint64 { return r.FunctionID } // Auditable returns all auditable/loggable parameters -func (r FunctionUpdate) GetRouteID() uint64 { +func (r ApigwFunctionUpdate) GetRouteID() uint64 { return r.RouteID } // Auditable returns all auditable/loggable parameters -func (r FunctionUpdate) GetWeight() uint64 { +func (r ApigwFunctionUpdate) GetWeight() uint64 { return r.Weight } // Auditable returns all auditable/loggable parameters -func (r FunctionUpdate) GetKind() types.ApigwFunctionKind { +func (r ApigwFunctionUpdate) GetKind() string { return r.Kind } // Auditable returns all auditable/loggable parameters -func (r FunctionUpdate) GetRef() string { +func (r ApigwFunctionUpdate) GetRef() string { return r.Ref } // Auditable returns all auditable/loggable parameters -func (r FunctionUpdate) GetParams() types.FuncParams { +func (r ApigwFunctionUpdate) GetParams() types.ApigwFuncParams { return r.Params } // Fill processes request and fills internal variables -func (r *FunctionUpdate) Fill(req *http.Request) (err error) { +func (r *ApigwFunctionUpdate) Fill(req *http.Request) (err error) { if strings.ToLower(req.Header.Get("content-type")) == "application/json" { err = json.NewDecoder(req.Body).Decode(r) @@ -477,7 +455,7 @@ func (r *FunctionUpdate) Fill(req *http.Request) (err error) { } if val, ok := req.Form["kind"]; ok && len(val) > 0 { - r.Kind, err = types.ApigwFunctionKind(val[0]), nil + r.Kind, err = val[0], nil if err != nil { return err } @@ -518,25 +496,25 @@ func (r *FunctionUpdate) Fill(req *http.Request) (err error) { return err } -// NewFunctionRead request -func NewFunctionRead() *FunctionRead { - return &FunctionRead{} +// NewApigwFunctionRead request +func NewApigwFunctionRead() *ApigwFunctionRead { + return &ApigwFunctionRead{} } // Auditable returns all auditable/loggable parameters -func (r FunctionRead) Auditable() map[string]interface{} { +func (r ApigwFunctionRead) Auditable() map[string]interface{} { return map[string]interface{}{ "functionID": r.FunctionID, } } // Auditable returns all auditable/loggable parameters -func (r FunctionRead) GetFunctionID() uint64 { +func (r ApigwFunctionRead) GetFunctionID() uint64 { return r.FunctionID } // Fill processes request and fills internal variables -func (r *FunctionRead) Fill(req *http.Request) (err error) { +func (r *ApigwFunctionRead) Fill(req *http.Request) (err error) { { var val string @@ -553,25 +531,25 @@ func (r *FunctionRead) Fill(req *http.Request) (err error) { return err } -// NewFunctionDelete request -func NewFunctionDelete() *FunctionDelete { - return &FunctionDelete{} +// NewApigwFunctionDelete request +func NewApigwFunctionDelete() *ApigwFunctionDelete { + return &ApigwFunctionDelete{} } // Auditable returns all auditable/loggable parameters -func (r FunctionDelete) Auditable() map[string]interface{} { +func (r ApigwFunctionDelete) Auditable() map[string]interface{} { return map[string]interface{}{ "functionID": r.FunctionID, } } // Auditable returns all auditable/loggable parameters -func (r FunctionDelete) GetFunctionID() uint64 { +func (r ApigwFunctionDelete) GetFunctionID() uint64 { return r.FunctionID } // Fill processes request and fills internal variables -func (r *FunctionDelete) Fill(req *http.Request) (err error) { +func (r *ApigwFunctionDelete) Fill(req *http.Request) (err error) { { var val string @@ -588,25 +566,25 @@ func (r *FunctionDelete) Fill(req *http.Request) (err error) { return err } -// NewFunctionUndelete request -func NewFunctionUndelete() *FunctionUndelete { - return &FunctionUndelete{} +// NewApigwFunctionUndelete request +func NewApigwFunctionUndelete() *ApigwFunctionUndelete { + return &ApigwFunctionUndelete{} } // Auditable returns all auditable/loggable parameters -func (r FunctionUndelete) Auditable() map[string]interface{} { +func (r ApigwFunctionUndelete) Auditable() map[string]interface{} { return map[string]interface{}{ "functionID": r.FunctionID, } } // Auditable returns all auditable/loggable parameters -func (r FunctionUndelete) GetFunctionID() uint64 { +func (r ApigwFunctionUndelete) GetFunctionID() uint64 { return r.FunctionID } // Fill processes request and fills internal variables -func (r *FunctionUndelete) Fill(req *http.Request) (err error) { +func (r *ApigwFunctionUndelete) Fill(req *http.Request) (err error) { { var val string @@ -623,25 +601,25 @@ func (r *FunctionUndelete) Fill(req *http.Request) (err error) { return err } -// NewFunctionDefinitions request -func NewFunctionDefinitions() *FunctionDefinitions { - return &FunctionDefinitions{} +// NewApigwFunctionDefinitions request +func NewApigwFunctionDefinitions() *ApigwFunctionDefinitions { + return &ApigwFunctionDefinitions{} } // Auditable returns all auditable/loggable parameters -func (r FunctionDefinitions) Auditable() map[string]interface{} { +func (r ApigwFunctionDefinitions) Auditable() map[string]interface{} { return map[string]interface{}{ "kind": r.Kind, } } // Auditable returns all auditable/loggable parameters -func (r FunctionDefinitions) GetKind() string { +func (r ApigwFunctionDefinitions) GetKind() string { return r.Kind } // Fill processes request and fills internal variables -func (r *FunctionDefinitions) Fill(req *http.Request) (err error) { +func (r *ApigwFunctionDefinitions) Fill(req *http.Request) (err error) { { // GET params diff --git a/system/rest/request/route.go b/system/rest/request/apigwRoute.go similarity index 78% rename from system/rest/request/route.go rename to system/rest/request/apigwRoute.go index 270f6bdb8..b2624db59 100644 --- a/system/rest/request/route.go +++ b/system/rest/request/apigwRoute.go @@ -34,11 +34,11 @@ var ( type ( // Internal API interface - RouteList struct { + ApigwRouteList struct { // RouteID GET parameter // // Filter by route ID - RouteID []string + RouteID []uint64 // Query GET parameter // @@ -76,7 +76,7 @@ type ( Sort string } - RouteCreate struct { + ApigwRouteCreate struct { // Endpoint POST parameter // // Route endpoint @@ -103,7 +103,7 @@ type ( Group uint64 `json:",string"` } - RouteUpdate struct { + ApigwRouteUpdate struct { // RouteID PATH parameter // // Route ID @@ -135,21 +135,21 @@ type ( Group uint64 `json:",string"` } - RouteRead struct { + ApigwRouteRead struct { // RouteID PATH parameter // // Route ID RouteID uint64 `json:",string"` } - RouteDelete struct { + ApigwRouteDelete struct { // RouteID PATH parameter // // Route ID RouteID uint64 `json:",string"` } - RouteUndelete struct { + ApigwRouteUndelete struct { // RouteID PATH parameter // // Route ID @@ -157,13 +157,13 @@ type ( } ) -// NewRouteList request -func NewRouteList() *RouteList { - return &RouteList{} +// NewApigwRouteList request +func NewApigwRouteList() *ApigwRouteList { + return &ApigwRouteList{} } // Auditable returns all auditable/loggable parameters -func (r RouteList) Auditable() map[string]interface{} { +func (r ApigwRouteList) Auditable() map[string]interface{} { return map[string]interface{}{ "routeID": r.RouteID, "query": r.Query, @@ -177,59 +177,59 @@ func (r RouteList) Auditable() map[string]interface{} { } // Auditable returns all auditable/loggable parameters -func (r RouteList) GetRouteID() []string { +func (r ApigwRouteList) GetRouteID() []uint64 { return r.RouteID } // Auditable returns all auditable/loggable parameters -func (r RouteList) GetQuery() string { +func (r ApigwRouteList) GetQuery() string { return r.Query } // Auditable returns all auditable/loggable parameters -func (r RouteList) GetDeleted() uint64 { +func (r ApigwRouteList) GetDeleted() uint64 { return r.Deleted } // Auditable returns all auditable/loggable parameters -func (r RouteList) GetDisabled() uint64 { +func (r ApigwRouteList) GetDisabled() uint64 { return r.Disabled } // Auditable returns all auditable/loggable parameters -func (r RouteList) GetLabels() map[string]string { +func (r ApigwRouteList) GetLabels() map[string]string { return r.Labels } // Auditable returns all auditable/loggable parameters -func (r RouteList) GetLimit() uint { +func (r ApigwRouteList) GetLimit() uint { return r.Limit } // Auditable returns all auditable/loggable parameters -func (r RouteList) GetPageCursor() string { +func (r ApigwRouteList) GetPageCursor() string { return r.PageCursor } // Auditable returns all auditable/loggable parameters -func (r RouteList) GetSort() string { +func (r ApigwRouteList) GetSort() string { return r.Sort } // Fill processes request and fills internal variables -func (r *RouteList) Fill(req *http.Request) (err error) { +func (r *ApigwRouteList) Fill(req *http.Request) (err error) { { // GET params tmp := req.URL.Query() if val, ok := tmp["routeID[]"]; ok { - r.RouteID, err = val, nil + r.RouteID, err = payload.ParseUint64s(val), nil if err != nil { return err } } else if val, ok := tmp["routeID"]; ok { - r.RouteID, err = val, nil + r.RouteID, err = payload.ParseUint64s(val), nil if err != nil { return err } @@ -286,13 +286,13 @@ func (r *RouteList) Fill(req *http.Request) (err error) { return err } -// NewRouteCreate request -func NewRouteCreate() *RouteCreate { - return &RouteCreate{} +// NewApigwRouteCreate request +func NewApigwRouteCreate() *ApigwRouteCreate { + return &ApigwRouteCreate{} } // Auditable returns all auditable/loggable parameters -func (r RouteCreate) Auditable() map[string]interface{} { +func (r ApigwRouteCreate) Auditable() map[string]interface{} { return map[string]interface{}{ "endpoint": r.Endpoint, "method": r.Method, @@ -303,32 +303,32 @@ func (r RouteCreate) Auditable() map[string]interface{} { } // Auditable returns all auditable/loggable parameters -func (r RouteCreate) GetEndpoint() string { +func (r ApigwRouteCreate) GetEndpoint() string { return r.Endpoint } // Auditable returns all auditable/loggable parameters -func (r RouteCreate) GetMethod() string { +func (r ApigwRouteCreate) GetMethod() string { return r.Method } // Auditable returns all auditable/loggable parameters -func (r RouteCreate) GetDebug() bool { +func (r ApigwRouteCreate) GetDebug() bool { return r.Debug } // Auditable returns all auditable/loggable parameters -func (r RouteCreate) GetEnabled() bool { +func (r ApigwRouteCreate) GetEnabled() bool { return r.Enabled } // Auditable returns all auditable/loggable parameters -func (r RouteCreate) GetGroup() uint64 { +func (r ApigwRouteCreate) GetGroup() uint64 { return r.Group } // Fill processes request and fills internal variables -func (r *RouteCreate) Fill(req *http.Request) (err error) { +func (r *ApigwRouteCreate) Fill(req *http.Request) (err error) { if strings.ToLower(req.Header.Get("content-type")) == "application/json" { err = json.NewDecoder(req.Body).Decode(r) @@ -387,13 +387,13 @@ func (r *RouteCreate) Fill(req *http.Request) (err error) { return err } -// NewRouteUpdate request -func NewRouteUpdate() *RouteUpdate { - return &RouteUpdate{} +// NewApigwRouteUpdate request +func NewApigwRouteUpdate() *ApigwRouteUpdate { + return &ApigwRouteUpdate{} } // Auditable returns all auditable/loggable parameters -func (r RouteUpdate) Auditable() map[string]interface{} { +func (r ApigwRouteUpdate) Auditable() map[string]interface{} { return map[string]interface{}{ "routeID": r.RouteID, "endpoint": r.Endpoint, @@ -405,37 +405,37 @@ func (r RouteUpdate) Auditable() map[string]interface{} { } // Auditable returns all auditable/loggable parameters -func (r RouteUpdate) GetRouteID() uint64 { +func (r ApigwRouteUpdate) GetRouteID() uint64 { return r.RouteID } // Auditable returns all auditable/loggable parameters -func (r RouteUpdate) GetEndpoint() string { +func (r ApigwRouteUpdate) GetEndpoint() string { return r.Endpoint } // Auditable returns all auditable/loggable parameters -func (r RouteUpdate) GetMethod() string { +func (r ApigwRouteUpdate) GetMethod() string { return r.Method } // Auditable returns all auditable/loggable parameters -func (r RouteUpdate) GetDebug() bool { +func (r ApigwRouteUpdate) GetDebug() bool { return r.Debug } // Auditable returns all auditable/loggable parameters -func (r RouteUpdate) GetEnabled() bool { +func (r ApigwRouteUpdate) GetEnabled() bool { return r.Enabled } // Auditable returns all auditable/loggable parameters -func (r RouteUpdate) GetGroup() uint64 { +func (r ApigwRouteUpdate) GetGroup() uint64 { return r.Group } // Fill processes request and fills internal variables -func (r *RouteUpdate) Fill(req *http.Request) (err error) { +func (r *ApigwRouteUpdate) Fill(req *http.Request) (err error) { if strings.ToLower(req.Header.Get("content-type")) == "application/json" { err = json.NewDecoder(req.Body).Decode(r) @@ -506,25 +506,25 @@ func (r *RouteUpdate) Fill(req *http.Request) (err error) { return err } -// NewRouteRead request -func NewRouteRead() *RouteRead { - return &RouteRead{} +// NewApigwRouteRead request +func NewApigwRouteRead() *ApigwRouteRead { + return &ApigwRouteRead{} } // Auditable returns all auditable/loggable parameters -func (r RouteRead) Auditable() map[string]interface{} { +func (r ApigwRouteRead) Auditable() map[string]interface{} { return map[string]interface{}{ "routeID": r.RouteID, } } // Auditable returns all auditable/loggable parameters -func (r RouteRead) GetRouteID() uint64 { +func (r ApigwRouteRead) GetRouteID() uint64 { return r.RouteID } // Fill processes request and fills internal variables -func (r *RouteRead) Fill(req *http.Request) (err error) { +func (r *ApigwRouteRead) Fill(req *http.Request) (err error) { { var val string @@ -541,25 +541,25 @@ func (r *RouteRead) Fill(req *http.Request) (err error) { return err } -// NewRouteDelete request -func NewRouteDelete() *RouteDelete { - return &RouteDelete{} +// NewApigwRouteDelete request +func NewApigwRouteDelete() *ApigwRouteDelete { + return &ApigwRouteDelete{} } // Auditable returns all auditable/loggable parameters -func (r RouteDelete) Auditable() map[string]interface{} { +func (r ApigwRouteDelete) Auditable() map[string]interface{} { return map[string]interface{}{ "routeID": r.RouteID, } } // Auditable returns all auditable/loggable parameters -func (r RouteDelete) GetRouteID() uint64 { +func (r ApigwRouteDelete) GetRouteID() uint64 { return r.RouteID } // Fill processes request and fills internal variables -func (r *RouteDelete) Fill(req *http.Request) (err error) { +func (r *ApigwRouteDelete) Fill(req *http.Request) (err error) { { var val string @@ -576,25 +576,25 @@ func (r *RouteDelete) Fill(req *http.Request) (err error) { return err } -// NewRouteUndelete request -func NewRouteUndelete() *RouteUndelete { - return &RouteUndelete{} +// NewApigwRouteUndelete request +func NewApigwRouteUndelete() *ApigwRouteUndelete { + return &ApigwRouteUndelete{} } // Auditable returns all auditable/loggable parameters -func (r RouteUndelete) Auditable() map[string]interface{} { +func (r ApigwRouteUndelete) Auditable() map[string]interface{} { return map[string]interface{}{ "routeID": r.RouteID, } } // Auditable returns all auditable/loggable parameters -func (r RouteUndelete) GetRouteID() uint64 { +func (r ApigwRouteUndelete) GetRouteID() uint64 { return r.RouteID } // Fill processes request and fills internal variables -func (r *RouteUndelete) Fill(req *http.Request) (err error) { +func (r *ApigwRouteUndelete) Fill(req *http.Request) (err error) { { var val string diff --git a/system/rest/router.go b/system/rest/router.go index 9fb27b3dc..90bb2963e 100644 --- a/system/rest/router.go +++ b/system/rest/router.go @@ -37,7 +37,7 @@ func MountRoutes(r chi.Router) { handlers.NewReminder(Reminder{}.New()).MountRoutes(r) handlers.NewActionlog(Actionlog{}.New()).MountRoutes(r) handlers.NewQueues(Queue{}.New()).MountRoutes(r) - handlers.NewRoute(Route{}.New()).MountRoutes(r) - handlers.NewFunction(Function{}.New()).MountRoutes(r) + handlers.NewApigwRoute(ApigwRoute{}.New()).MountRoutes(r) + handlers.NewApigwFunction(ApigwFunction{}.New()).MountRoutes(r) }) } diff --git a/system/service/access_control.gen.go b/system/service/access_control.gen.go index 16063b93a..fe03d1eaa 100644 --- a/system/service/access_control.gen.go +++ b/system/service/access_control.gen.go @@ -7,10 +7,11 @@ package service // // Definitions file that controls how this file is generated: +// - system.apigw-function.yaml +// - system.apigw-route.yaml // - system.application.yaml // - system.auth-client.yaml // - system.role.yaml -// - system.route.yaml // - system.template.yaml // - system.user.yaml // - system.yaml @@ -63,6 +64,36 @@ func (svc accessControl) Effective(ctx context.Context, rr ...rbac.Resource) (ee func (svc accessControl) List() (out []map[string]string) { def := []map[string]string{ + { + "type": types.ApigwFunctionResourceType, + "any": types.ApigwFunctionRbacResource(0), + "op": "read", + }, + { + "type": types.ApigwFunctionResourceType, + "any": types.ApigwFunctionRbacResource(0), + "op": "update", + }, + { + "type": types.ApigwFunctionResourceType, + "any": types.ApigwFunctionRbacResource(0), + "op": "delete", + }, + { + "type": types.ApigwRouteResourceType, + "any": types.ApigwRouteRbacResource(0), + "op": "read", + }, + { + "type": types.ApigwRouteResourceType, + "any": types.ApigwRouteRbacResource(0), + "op": "update", + }, + { + "type": types.ApigwRouteResourceType, + "any": types.ApigwRouteRbacResource(0), + "op": "delete", + }, { "type": types.ApplicationResourceType, "any": types.ApplicationRbacResource(0), @@ -118,21 +149,6 @@ func (svc accessControl) List() (out []map[string]string) { "any": types.RoleRbacResource(0), "op": "members.manage", }, - { - "type": types.RouteResourceType, - "any": types.RouteRbacResource(0), - "op": "read", - }, - { - "type": types.RouteResourceType, - "any": types.RouteRbacResource(0), - "op": "update", - }, - { - "type": types.RouteResourceType, - "any": types.RouteRbacResource(0), - "op": "delete", - }, { "type": types.TemplateResourceType, "any": types.TemplateRbacResource(0), @@ -293,6 +309,21 @@ func (svc accessControl) List() (out []map[string]string) { "any": types.ComponentRbacResource(), "op": "api-gw-route.create", }, + { + "type": types.ComponentResourceType, + "any": types.ComponentRbacResource(), + "op": "api-gw-routes.search", + }, + { + "type": types.ComponentResourceType, + "any": types.ComponentRbacResource(), + "op": "api-gw-function.create", + }, + { + "type": types.ComponentResourceType, + "any": types.ComponentRbacResource(), + "op": "api-gw-functions.search", + }, } func(svc interface{}) { @@ -355,6 +386,48 @@ func (svc accessControl) FindRulesByRoleID(ctx context.Context, roleID uint64) ( return svc.rbac.FindRulesByRoleID(roleID), nil } +// CanReadApigwFunction checks if current user can read api gateway function +// +// This function is auto-generated +func (svc accessControl) CanReadApigwFunction(ctx context.Context, r *types.ApigwFunction) bool { + return svc.can(ctx, "read", r) +} + +// CanUpdateApigwFunction checks if current user can update api gateway function +// +// This function is auto-generated +func (svc accessControl) CanUpdateApigwFunction(ctx context.Context, r *types.ApigwFunction) bool { + return svc.can(ctx, "update", r) +} + +// CanDeleteApigwFunction checks if current user can delete api gateway function +// +// This function is auto-generated +func (svc accessControl) CanDeleteApigwFunction(ctx context.Context, r *types.ApigwFunction) bool { + return svc.can(ctx, "delete", r) +} + +// CanReadApigwRoute checks if current user can read api gateway route +// +// This function is auto-generated +func (svc accessControl) CanReadApigwRoute(ctx context.Context, r *types.ApigwRoute) bool { + return svc.can(ctx, "read", r) +} + +// CanUpdateApigwRoute checks if current user can update api gateway route +// +// This function is auto-generated +func (svc accessControl) CanUpdateApigwRoute(ctx context.Context, r *types.ApigwRoute) bool { + return svc.can(ctx, "update", r) +} + +// CanDeleteApigwRoute checks if current user can delete api gateway route +// +// This function is auto-generated +func (svc accessControl) CanDeleteApigwRoute(ctx context.Context, r *types.ApigwRoute) bool { + return svc.can(ctx, "delete", r) +} + // CanReadApplication checks if current user can read application // // This function is auto-generated @@ -432,27 +505,6 @@ func (svc accessControl) CanManageMembersOnRole(ctx context.Context, r *types.Ro return svc.can(ctx, "members.manage", r) } -// CanReadRoute checks if current user can read api gateway route -// -// This function is auto-generated -func (svc accessControl) CanReadRoute(ctx context.Context, r *types.Route) bool { - return svc.can(ctx, "read", r) -} - -// CanUpdateRoute checks if current user can update api gateway route -// -// This function is auto-generated -func (svc accessControl) CanUpdateRoute(ctx context.Context, r *types.Route) bool { - return svc.can(ctx, "update", r) -} - -// CanDeleteRoute checks if current user can delete api gateway route -// -// This function is auto-generated -func (svc accessControl) CanDeleteRoute(ctx context.Context, r *types.Route) bool { - return svc.can(ctx, "delete", r) -} - // CanReadTemplate checks if current user can read template // // This function is auto-generated @@ -677,19 +729,42 @@ func (svc accessControl) CanCreateApiGwRoute(ctx context.Context) bool { return svc.can(ctx, "api-gw-route.create", &types.Component{}) } +// CanSearchApiGwRoutes checks if current user can list search or filter api gateway routes +// +// This function is auto-generated +func (svc accessControl) CanSearchApiGwRoutes(ctx context.Context) bool { + return svc.can(ctx, "api-gw-routes.search", &types.Component{}) +} + +// CanCreateApiGwFunction checks if current user can add api gateway function to route +// +// This function is auto-generated +func (svc accessControl) CanCreateApiGwFunction(ctx context.Context) bool { + return svc.can(ctx, "api-gw-function.create", &types.Component{}) +} + +// CanSearchApiGwFunctions checks if current user can list, search or filter functions +// +// This function is auto-generated +func (svc accessControl) CanSearchApiGwFunctions(ctx context.Context) bool { + return svc.can(ctx, "api-gw-functions.search", &types.Component{}) +} + // rbacResourceValidator validates known component's resource by routing it to the appropriate validator // // This function is auto-generated func rbacResourceValidator(r string, oo ...string) error { switch rbac.ResourceType(r) { + case types.ApigwFunctionResourceType: + return rbacApigwFunctionResourceValidator(r, oo...) + case types.ApigwRouteResourceType: + return rbacApigwRouteResourceValidator(r, oo...) case types.ApplicationResourceType: return rbacApplicationResourceValidator(r, oo...) case types.AuthClientResourceType: return rbacAuthClientResourceValidator(r, oo...) case types.RoleResourceType: return rbacRoleResourceValidator(r, oo...) - case types.RouteResourceType: - return rbacRouteResourceValidator(r, oo...) case types.TemplateResourceType: return rbacTemplateResourceValidator(r, oo...) case types.UserResourceType: @@ -706,6 +781,18 @@ func rbacResourceValidator(r string, oo ...string) error { // This function is auto-generated func rbacResourceOperations(r string) map[string]bool { switch rbac.ResourceType(r) { + case types.ApigwFunctionResourceType: + return map[string]bool{ + "read": true, + "update": true, + "delete": true, + } + case types.ApigwRouteResourceType: + return map[string]bool{ + "read": true, + "update": true, + "delete": true, + } case types.ApplicationResourceType: return map[string]bool{ "read": true, @@ -726,12 +813,6 @@ func rbacResourceOperations(r string) map[string]bool { "delete": true, "members.manage": true, } - case types.RouteResourceType: - return map[string]bool{ - "read": true, - "update": true, - "delete": true, - } case types.TemplateResourceType: return map[string]bool{ "read": true, @@ -772,6 +853,111 @@ func rbacResourceOperations(r string) map[string]bool { "queue.create": true, "queues.search": true, "api-gw-route.create": true, + "api-gw-routes.search": true, + "api-gw-function.create": true, + "api-gw-functions.search": true, + } + } + + return nil +} + +// rbacApigwFunctionResourceValidator checks validity of rbac resource and operations +// +// Can be called without operations to check for validity of resource string only +// +// This function is auto-generated +func rbacApigwFunctionResourceValidator(r string, oo ...string) error { + defOps := rbacResourceOperations(r) + for _, o := range oo { + if !defOps[o] { + return fmt.Errorf("invalid operation '%s' for system ApigwFunction resource", o) + } + } + + if !strings.HasPrefix(r, types.ApigwFunctionResourceType) { + // expecting resource to always include path + return fmt.Errorf("invalid resource type") + } + + const sep = "/" + var ( + specIdUsed = true + + pp = strings.Split(strings.Trim(r[len(types.ApigwFunctionResourceType):], sep), sep) + prc = []string{ + "ID", + } + ) + + if len(pp) != len(prc) { + return fmt.Errorf("invalid resource path structure") + } + + for i, p := range pp { + if p == "*" { + if !specIdUsed { + return fmt.Errorf("invalid resource path wildcard level (%d) for ApigwFunction", i) + } + + specIdUsed = false + continue + } + + specIdUsed = true + if _, err := cast.ToUint64E(p); err != nil { + return fmt.Errorf("invalid reference for %s: '%s'", prc[i], p) + } + } + + return nil +} + +// rbacApigwRouteResourceValidator checks validity of rbac resource and operations +// +// Can be called without operations to check for validity of resource string only +// +// This function is auto-generated +func rbacApigwRouteResourceValidator(r string, oo ...string) error { + defOps := rbacResourceOperations(r) + for _, o := range oo { + if !defOps[o] { + return fmt.Errorf("invalid operation '%s' for system ApigwRoute resource", o) + } + } + + if !strings.HasPrefix(r, types.ApigwRouteResourceType) { + // expecting resource to always include path + return fmt.Errorf("invalid resource type") + } + + const sep = "/" + var ( + specIdUsed = true + + pp = strings.Split(strings.Trim(r[len(types.ApigwRouteResourceType):], sep), sep) + prc = []string{ + "ID", + } + ) + + if len(pp) != len(prc) { + return fmt.Errorf("invalid resource path structure") + } + + for i, p := range pp { + if p == "*" { + if !specIdUsed { + return fmt.Errorf("invalid resource path wildcard level (%d) for ApigwRoute", i) + } + + specIdUsed = false + continue + } + + specIdUsed = true + if _, err := cast.ToUint64E(p); err != nil { + return fmt.Errorf("invalid reference for %s: '%s'", prc[i], p) } } @@ -816,16 +1002,11 @@ func rbacApplicationResourceValidator(r string, oo ...string) error { return fmt.Errorf("invalid resource path wildcard level (%d) for Application", i) } - specIdUsed = false - continue - } - - specIdUsed = true - if _, err := cast.ToUint64E(p); err != nil { - return fmt.Errorf("invalid reference for %s: '%s'", prc[i], p) + if _, err := cast.ToUint64E(pp[i]); err != nil { + return fmt.Errorf("invalid reference for %s: '%s'", prc[i], pp[i]) + } } } - return nil } @@ -921,52 +1102,6 @@ func rbacRoleResourceValidator(r string, oo ...string) error { return nil } -// rbacRouteResourceValidator checks validity of rbac resource and operations -// -// Can be called without operations to check for validity of resource string only -// -// This function is auto-generated -func rbacRouteResourceValidator(r string, oo ...string) error { - defOps := rbacResourceOperations(r) - for _, o := range oo { - if !defOps[o] { - return fmt.Errorf("invalid operation '%s' for system Route resource", o) - } - } - - if !strings.HasPrefix(r, types.RouteResourceType) { - // expecting resource to always include path - return fmt.Errorf("invalid resource type") - } - - const sep = "/" - var ( - specIdUsed = true - - pp = strings.Split(strings.Trim(r[len(types.RouteResourceType):], sep), sep) - prc = []string{ - "ID", - } - ) - - if len(pp) != len(prc) { - return fmt.Errorf("invalid resource path structure") - } - - for i, p := range pp { - if p == "*" { - if !specIdUsed { - return fmt.Errorf("invalid resource path wildcard level (%d) for Route", i) - } - - if _, err := cast.ToUint64E(pp[i]); err != nil { - return fmt.Errorf("invalid reference for %s: '%s'", prc[i], pp[i]) - } - } - } - return nil -} - // rbacTemplateResourceValidator checks validity of rbac resource and operations // // Can be called without operations to check for validity of resource string only diff --git a/system/service/apigw_function.go b/system/service/apigw_function.go new file mode 100644 index 000000000..b50195394 --- /dev/null +++ b/system/service/apigw_function.go @@ -0,0 +1,249 @@ +package service + +import ( + "context" + + "github.com/cortezaproject/corteza-server/pkg/actionlog" + "github.com/cortezaproject/corteza-server/pkg/apigw" + a "github.com/cortezaproject/corteza-server/pkg/auth" + "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/system/types" +) + +type ( + apigwFunction struct { + actionlog actionlog.Recorder + store store.Storer + ac functionAccessController + } + + functionAccessController interface { + CanSearchApiGwFunctions(context.Context) bool + + CanCreateApiGwFunction(context.Context) bool + CanReadApigwFunction(context.Context, *types.ApigwFunction) bool + CanUpdateApigwFunction(context.Context, *types.ApigwFunction) bool + CanDeleteApigwFunction(context.Context, *types.ApigwFunction) bool + } +) + +func Function() *apigwFunction { + return (&apigwFunction{ + ac: DefaultAccessControl, + actionlog: DefaultActionlog, + store: DefaultStore, + }) +} + +func (svc *apigwFunction) FindByID(ctx context.Context, ID uint64) (q *types.ApigwFunction, err error) { + var ( + rProps = &apigwFunctionActionProps{} + ) + + err = func() error { + if ID == 0 { + return ApigwFunctionErrInvalidID() + } + + if !svc.ac.CanSearchApiGwFunctions(ctx) { + return ApigwFunctionErrNotAllowedToRead(rProps) + } + + if q, err = store.LookupApigwFunctionByID(ctx, svc.store, ID); err != nil { + return TemplateErrInvalidID().Wrap(err) + } + + rProps.setFunction(q) + + return nil + }() + + return q, svc.recordAction(ctx, rProps, ApigwFunctionActionLookup, err) +} + +func (svc *apigwFunction) Create(ctx context.Context, new *types.ApigwFunction) (q *types.ApigwFunction, err error) { + var ( + qProps = &apigwFunctionActionProps{function: new} + ) + + err = func() (err error) { + if !svc.ac.CanCreateApiGwFunction(ctx) { + return ApigwFunctionErrNotAllowedToCreate(qProps) + } + + // Set new values after beforeCreate events are emitted + new.ID = nextID() + new.CreatedAt = *now() + new.CreatedBy = a.GetIdentityFromContext(ctx).Identity() + + if _, err = DefaultRoute.FindByID(ctx, new.Route); err != nil { + return ApigwFunctionErrNotFound(qProps) + } + + if err = store.CreateApigwFunction(ctx, svc.store, new); err != nil { + return err + } + + q = new + // send the signal to reload all functions + apigw.Service().Reload(ctx) + + return nil + }() + + return q, svc.recordAction(ctx, qProps, ApigwFunctionActionCreate, err) +} + +func (svc *apigwFunction) Update(ctx context.Context, upd *types.ApigwFunction) (q *types.ApigwFunction, err error) { + var ( + qProps = &apigwFunctionActionProps{function: upd} + qq *types.ApigwFunction + e error + ) + + err = func() (err error) { + if qq, e = store.LookupApigwFunctionByID(ctx, svc.store, upd.ID); e != nil { + return ApigwFunctionErrNotFound(qProps) + } + + if !svc.ac.CanUpdateApigwFunction(ctx, qq) { + return ApigwFunctionErrNotAllowedToUpdate(qProps) + } + + if _, err = DefaultRoute.FindByID(ctx, upd.Route); err != nil { + return err + } + + if qq, e = store.LookupApigwFunctionByID(ctx, svc.store, upd.ID); e == nil && qq == nil { + return ApigwFunctionErrNotFound(qProps) + } + + upd.UpdatedAt = now() + upd.CreatedAt = qq.CreatedAt + upd.UpdatedBy = a.GetIdentityFromContext(ctx).Identity() + + if err = store.UpdateApigwFunction(ctx, svc.store, upd); err != nil { + return + } + + q = upd + + // send the signal to reload all function + apigw.Service().Reload(ctx) + + return nil + }() + + return q, svc.recordAction(ctx, qProps, ApigwFunctionActionUpdate, err) +} + +func (svc *apigwFunction) DeleteByID(ctx context.Context, ID uint64) (err error) { + var ( + qProps = &apigwFunctionActionProps{} + q *types.ApigwFunction + ) + + err = func() (err error) { + if q, err = store.LookupApigwFunctionByID(ctx, svc.store, ID); err != nil { + return ApigwFunctionErrNotFound(qProps) + } + + if !svc.ac.CanDeleteApigwFunction(ctx, q) { + return ApigwFunctionErrNotAllowedToDelete(qProps) + } + + qProps.setFunction(q) + + q.DeletedAt = now() + q.DeletedBy = a.GetIdentityFromContext(ctx).Identity() + + if err = store.UpdateApigwFunction(ctx, svc.store, q); err != nil { + return + } + + // send the signal to reload all queues + apigw.Service().Reload(ctx) + + return nil + }() + + return svc.recordAction(ctx, qProps, ApigwFunctionActionDelete, err) +} + +func (svc *apigwFunction) UndeleteByID(ctx context.Context, ID uint64) (err error) { + var ( + qProps = &apigwFunctionActionProps{} + q *types.ApigwFunction + ) + + err = func() (err error) { + if q, err = store.LookupApigwFunctionByID(ctx, svc.store, ID); err != nil { + return ApigwFunctionErrNotFound(qProps) + } + + if !svc.ac.CanDeleteApigwFunction(ctx, q) { + return ApigwFunctionErrNotAllowedToDelete(qProps) + } + + qProps.setFunction(q) + + q.DeletedAt = nil + q.UpdatedBy = a.GetIdentityFromContext(ctx).Identity() + + if err = store.UpdateApigwFunction(ctx, svc.store, q); err != nil { + return + } + + // send the signal to reload all queues + apigw.Service().Reload(ctx) + + return nil + }() + + return svc.recordAction(ctx, qProps, ApigwFunctionActionDelete, err) +} + +func (svc *apigwFunction) Search(ctx context.Context, filter types.ApigwFunctionFilter) (r types.ApigwFunctionSet, f types.ApigwFunctionFilter, err error) { + var ( + aProps = &apigwFunctionActionProps{search: &filter} + ) + + // For each fetched item, store backend will check if it is valid or not + filter.Check = func(res *types.ApigwFunction) (bool, error) { + if !svc.ac.CanReadApigwFunction(ctx, res) { + return false, nil + } + + return true, nil + } + + err = func() error { + if r, f, err = store.SearchApigwFunctions(ctx, svc.store, filter); err != nil { + return err + } + + return nil + }() + + return r, f, svc.recordAction(ctx, aProps, ApigwFunctionActionSearch, err) +} + +func (svc *apigwFunction) Definitions(ctx context.Context, kind string) (l interface{}, err error) { + var ( + qProps = &apigwFunctionActionProps{} + ) + + err = func() error { + if !svc.ac.CanSearchApiGwFunctions(ctx) { + return ApigwFunctionErrNotAllowedToRead(qProps) + } + + // get the definitions from registry + l = apigw.Service().Funcs(kind) + + return nil + }() + + return l, svc.recordAction(ctx, qProps, ApigwFunctionActionSearch, err) + +} diff --git a/system/service/function_actions.gen.go b/system/service/apigw_function_actions.gen.go similarity index 50% rename from system/service/function_actions.gen.go rename to system/service/apigw_function_actions.gen.go index de483497f..0a08fa64b 100644 --- a/system/service/function_actions.gen.go +++ b/system/service/apigw_function_actions.gen.go @@ -6,7 +6,7 @@ package service // the code is regenerated. // // Definitions file that controls how this file is generated: -// system/service/function_actions.yaml +// system/service/apigw_function_actions.yaml import ( "context" @@ -19,12 +19,12 @@ import ( ) type ( - functionActionProps struct { - function *types.Function - search *types.FunctionFilter + apigwFunctionActionProps struct { + function *types.ApigwFunction + search *types.ApigwFunctionFilter } - functionAction struct { + apigwFunctionAction struct { timestamp time.Time resource string action string @@ -34,11 +34,11 @@ type ( // prefix for error when action fails errorMessage string - props *functionActionProps + props *apigwFunctionActionProps } - functionLogMetaKey struct{} - functionPropsMetaKey struct{} + apigwFunctionLogMetaKey struct{} + apigwFunctionPropsMetaKey struct{} ) var ( @@ -49,33 +49,33 @@ var ( // ********************************************************************************************************************* // ********************************************************************************************************************* // Props methods -// setFunction updates functionActionProps's function +// setFunction updates apigwFunctionActionProps's function // // Allows method chaining // // This function is auto-generated. // -func (p *functionActionProps) setFunction(function *types.Function) *functionActionProps { +func (p *apigwFunctionActionProps) setFunction(function *types.ApigwFunction) *apigwFunctionActionProps { p.function = function return p } -// setSearch updates functionActionProps's search +// setSearch updates apigwFunctionActionProps's search // // Allows method chaining // // This function is auto-generated. // -func (p *functionActionProps) setSearch(search *types.FunctionFilter) *functionActionProps { +func (p *apigwFunctionActionProps) setSearch(search *types.ApigwFunctionFilter) *apigwFunctionActionProps { p.search = search return p } -// Serialize converts functionActionProps to actionlog.Meta +// Serialize converts apigwFunctionActionProps to actionlog.Meta // // This function is auto-generated. // -func (p functionActionProps) Serialize() actionlog.Meta { +func (p apigwFunctionActionProps) Serialize() actionlog.Meta { var ( m = make(actionlog.Meta) ) @@ -94,7 +94,7 @@ func (p functionActionProps) Serialize() actionlog.Meta { // // This function is auto-generated. // -func (p functionActionProps) Format(in string, err error) string { +func (p apigwFunctionActionProps) Format(in string, err error) string { var ( pairs = []string{"{err}"} // first non-empty string @@ -148,8 +148,8 @@ func (p functionActionProps) Format(in string, err error) string { // // This function is auto-generated. // -func (a *functionAction) String() string { - var props = &functionActionProps{} +func (a *apigwFunctionAction) String() string { + var props = &apigwFunctionActionProps{} if a.props != nil { props = a.props @@ -158,7 +158,7 @@ func (a *functionAction) String() string { return props.Format(a.log, nil) } -func (e *functionAction) ToAction() *actionlog.Action { +func (e *apigwFunctionAction) ToAction() *actionlog.Action { return &actionlog.Action{ Resource: e.resource, Action: e.action, @@ -172,12 +172,12 @@ func (e *functionAction) ToAction() *actionlog.Action { // ********************************************************************************************************************* // Action constructors -// FunctionActionSearch returns "system:function.search" action +// ApigwFunctionActionSearch returns "system:function.search" action // // This function is auto-generated. // -func FunctionActionSearch(props ...*functionActionProps) *functionAction { - a := &functionAction{ +func ApigwFunctionActionSearch(props ...*apigwFunctionActionProps) *apigwFunctionAction { + a := &apigwFunctionAction{ timestamp: time.Now(), resource: "system:function", action: "search", @@ -192,12 +192,12 @@ func FunctionActionSearch(props ...*functionActionProps) *functionAction { return a } -// FunctionActionLookup returns "system:function.lookup" action +// ApigwFunctionActionLookup returns "system:function.lookup" action // // This function is auto-generated. // -func FunctionActionLookup(props ...*functionActionProps) *functionAction { - a := &functionAction{ +func ApigwFunctionActionLookup(props ...*apigwFunctionActionProps) *apigwFunctionAction { + a := &apigwFunctionAction{ timestamp: time.Now(), resource: "system:function", action: "lookup", @@ -212,12 +212,12 @@ func FunctionActionLookup(props ...*functionActionProps) *functionAction { return a } -// FunctionActionCreate returns "system:function.create" action +// ApigwFunctionActionCreate returns "system:function.create" action // // This function is auto-generated. // -func FunctionActionCreate(props ...*functionActionProps) *functionAction { - a := &functionAction{ +func ApigwFunctionActionCreate(props ...*apigwFunctionActionProps) *apigwFunctionAction { + a := &apigwFunctionAction{ timestamp: time.Now(), resource: "system:function", action: "create", @@ -232,12 +232,12 @@ func FunctionActionCreate(props ...*functionActionProps) *functionAction { return a } -// FunctionActionUpdate returns "system:function.update" action +// ApigwFunctionActionUpdate returns "system:function.update" action // // This function is auto-generated. // -func FunctionActionUpdate(props ...*functionActionProps) *functionAction { - a := &functionAction{ +func ApigwFunctionActionUpdate(props ...*apigwFunctionActionProps) *apigwFunctionAction { + a := &apigwFunctionAction{ timestamp: time.Now(), resource: "system:function", action: "update", @@ -252,12 +252,12 @@ func FunctionActionUpdate(props ...*functionActionProps) *functionAction { return a } -// FunctionActionDelete returns "system:function.delete" action +// ApigwFunctionActionDelete returns "system:function.delete" action // // This function is auto-generated. // -func FunctionActionDelete(props ...*functionActionProps) *functionAction { - a := &functionAction{ +func ApigwFunctionActionDelete(props ...*apigwFunctionActionProps) *apigwFunctionAction { + a := &apigwFunctionAction{ timestamp: time.Now(), resource: "system:function", action: "delete", @@ -272,12 +272,12 @@ func FunctionActionDelete(props ...*functionActionProps) *functionAction { return a } -// FunctionActionUndelete returns "system:function.undelete" action +// ApigwFunctionActionUndelete returns "system:function.undelete" action // // This function is auto-generated. // -func FunctionActionUndelete(props ...*functionActionProps) *functionAction { - a := &functionAction{ +func ApigwFunctionActionUndelete(props ...*apigwFunctionActionProps) *apigwFunctionAction { + a := &apigwFunctionAction{ timestamp: time.Now(), resource: "system:function", action: "undelete", @@ -296,13 +296,13 @@ func FunctionActionUndelete(props ...*functionActionProps) *functionAction { // ********************************************************************************************************************* // Error constructors -// FunctionErrGeneric returns "system:function.generic" as *errors.Error +// ApigwFunctionErrGeneric returns "system:function.generic" as *errors.Error // // // This function is auto-generated. // -func FunctionErrGeneric(mm ...*functionActionProps) *errors.Error { - var p = &functionActionProps{} +func ApigwFunctionErrGeneric(mm ...*apigwFunctionActionProps) *errors.Error { + var p = &apigwFunctionActionProps{} if len(mm) > 0 { p = mm[0] } @@ -316,8 +316,8 @@ func FunctionErrGeneric(mm ...*functionActionProps) *errors.Error { errors.Meta("resource", "system:function"), // action log entry; no formatting, it will be applied inside recordAction fn. - errors.Meta(functionLogMetaKey{}, "{err}"), - errors.Meta(functionPropsMetaKey{}, p), + errors.Meta(apigwFunctionLogMetaKey{}, "{err}"), + errors.Meta(apigwFunctionPropsMetaKey{}, p), errors.StackSkip(1), ) @@ -328,13 +328,13 @@ func FunctionErrGeneric(mm ...*functionActionProps) *errors.Error { return e } -// FunctionErrNotFound returns "system:function.notFound" as *errors.Error +// ApigwFunctionErrNotFound returns "system:function.notFound" as *errors.Error // // // This function is auto-generated. // -func FunctionErrNotFound(mm ...*functionActionProps) *errors.Error { - var p = &functionActionProps{} +func ApigwFunctionErrNotFound(mm ...*apigwFunctionActionProps) *errors.Error { + var p = &apigwFunctionActionProps{} if len(mm) > 0 { p = mm[0] } @@ -347,7 +347,7 @@ func FunctionErrNotFound(mm ...*functionActionProps) *errors.Error { errors.Meta("type", "notFound"), errors.Meta("resource", "system:function"), - errors.Meta(functionPropsMetaKey{}, p), + errors.Meta(apigwFunctionPropsMetaKey{}, p), errors.StackSkip(1), ) @@ -358,13 +358,13 @@ func FunctionErrNotFound(mm ...*functionActionProps) *errors.Error { return e } -// FunctionErrInvalidID returns "system:function.invalidID" as *errors.Error +// ApigwFunctionErrInvalidID returns "system:function.invalidID" as *errors.Error // // // This function is auto-generated. // -func FunctionErrInvalidID(mm ...*functionActionProps) *errors.Error { - var p = &functionActionProps{} +func ApigwFunctionErrInvalidID(mm ...*apigwFunctionActionProps) *errors.Error { + var p = &apigwFunctionActionProps{} if len(mm) > 0 { p = mm[0] } @@ -377,7 +377,7 @@ func FunctionErrInvalidID(mm ...*functionActionProps) *errors.Error { errors.Meta("type", "invalidID"), errors.Meta("resource", "system:function"), - errors.Meta(functionPropsMetaKey{}, p), + errors.Meta(apigwFunctionPropsMetaKey{}, p), errors.StackSkip(1), ) @@ -388,13 +388,13 @@ func FunctionErrInvalidID(mm ...*functionActionProps) *errors.Error { return e } -// FunctionErrInvalidRoute returns "system:function.invalidRoute" as *errors.Error +// ApigwFunctionErrInvalidRoute returns "system:function.invalidRoute" as *errors.Error // // // This function is auto-generated. // -func FunctionErrInvalidRoute(mm ...*functionActionProps) *errors.Error { - var p = &functionActionProps{} +func ApigwFunctionErrInvalidRoute(mm ...*apigwFunctionActionProps) *errors.Error { + var p = &apigwFunctionActionProps{} if len(mm) > 0 { p = mm[0] } @@ -407,7 +407,167 @@ func FunctionErrInvalidRoute(mm ...*functionActionProps) *errors.Error { errors.Meta("type", "invalidRoute"), errors.Meta("resource", "system:function"), - errors.Meta(functionPropsMetaKey{}, p), + errors.Meta(apigwFunctionPropsMetaKey{}, p), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// ApigwFunctionErrNotAllowedToCreate returns "system:function.notAllowedToCreate" as *errors.Error +// +// +// This function is auto-generated. +// +func ApigwFunctionErrNotAllowedToCreate(mm ...*apigwFunctionActionProps) *errors.Error { + var p = &apigwFunctionActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("not allowed to create a function", nil), + + errors.Meta("type", "notAllowedToCreate"), + errors.Meta("resource", "system:function"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(apigwFunctionLogMetaKey{}, "failed to create a route; insufficient permissions"), + errors.Meta(apigwFunctionPropsMetaKey{}, p), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// ApigwFunctionErrNotAllowedToRead returns "system:function.notAllowedToRead" as *errors.Error +// +// +// This function is auto-generated. +// +func ApigwFunctionErrNotAllowedToRead(mm ...*apigwFunctionActionProps) *errors.Error { + var p = &apigwFunctionActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("not allowed to read this function", nil), + + errors.Meta("type", "notAllowedToRead"), + errors.Meta("resource", "system:function"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(apigwFunctionLogMetaKey{}, "failed to read {function}; insufficient permissions"), + errors.Meta(apigwFunctionPropsMetaKey{}, p), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// ApigwFunctionErrNotAllowedToUpdate returns "system:function.notAllowedToUpdate" as *errors.Error +// +// +// This function is auto-generated. +// +func ApigwFunctionErrNotAllowedToUpdate(mm ...*apigwFunctionActionProps) *errors.Error { + var p = &apigwFunctionActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("not allowed to update this function", nil), + + errors.Meta("type", "notAllowedToUpdate"), + errors.Meta("resource", "system:function"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(apigwFunctionLogMetaKey{}, "failed to update {function}; insufficient permissions"), + errors.Meta(apigwFunctionPropsMetaKey{}, p), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// ApigwFunctionErrNotAllowedToDelete returns "system:function.notAllowedToDelete" as *errors.Error +// +// +// This function is auto-generated. +// +func ApigwFunctionErrNotAllowedToDelete(mm ...*apigwFunctionActionProps) *errors.Error { + var p = &apigwFunctionActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("not allowed to delete this function", nil), + + errors.Meta("type", "notAllowedToDelete"), + errors.Meta("resource", "system:function"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(apigwFunctionLogMetaKey{}, "failed to delete {function}; insufficient permissions"), + errors.Meta(apigwFunctionPropsMetaKey{}, p), + + errors.StackSkip(1), + ) + + if len(mm) > 0 { + } + + return e +} + +// ApigwFunctionErrNotAllowedToUndelete returns "system:function.notAllowedToUndelete" as *errors.Error +// +// +// This function is auto-generated. +// +func ApigwFunctionErrNotAllowedToUndelete(mm ...*apigwFunctionActionProps) *errors.Error { + var p = &apigwFunctionActionProps{} + if len(mm) > 0 { + p = mm[0] + } + + var e = errors.New( + errors.KindInternal, + + p.Format("not allowed to undelete this function", nil), + + errors.Meta("type", "notAllowedToUndelete"), + errors.Meta("resource", "system:function"), + + // action log entry; no formatting, it will be applied inside recordAction fn. + errors.Meta(apigwFunctionLogMetaKey{}, "failed to undelete {function}; insufficient permissions"), + errors.Meta(apigwFunctionPropsMetaKey{}, p), errors.StackSkip(1), ) @@ -427,7 +587,7 @@ func FunctionErrInvalidRoute(mm ...*functionActionProps) *errors.Error { // // This function is auto-generated. // -func (svc function) recordAction(ctx context.Context, props *functionActionProps, actionFn func(...*functionActionProps) *functionAction, err error) error { +func (svc apigwFunction) recordAction(ctx context.Context, props *apigwFunctionActionProps, actionFn func(...*apigwFunctionActionProps) *apigwFunctionAction, err error) error { if svc.actionlog == nil || actionFn == nil { // action log disabled or no action fn passed, return error as-is return err @@ -448,10 +608,10 @@ func (svc function) recordAction(ctx context.Context, props *functionActionProps a.Error = err.Error() a.Severity = actionlog.Severity(m.AsInt("severity")) - a.Description = props.Format(m.AsString(functionLogMetaKey{}), err) + a.Description = props.Format(m.AsString(apigwFunctionLogMetaKey{}), err) - if p, has := m[functionPropsMetaKey{}]; has { - a.Meta = p.(*functionActionProps).Serialize() + if p, has := m[apigwFunctionPropsMetaKey{}]; has { + a.Meta = p.(*apigwFunctionActionProps).Serialize() } svc.actionlog.Record(ctx, a) diff --git a/system/service/function_actions.yaml b/system/service/apigw_function_actions.yaml similarity index 56% rename from system/service/function_actions.yaml rename to system/service/apigw_function_actions.yaml index 0cff17d00..400b43c5e 100644 --- a/system/service/function_actions.yaml +++ b/system/service/apigw_function_actions.yaml @@ -1,7 +1,7 @@ # List of loggable service actions resource: system:function -service: function +service: apigwFunction # Default sensitivity for actions defaultActionSeverity: notice @@ -14,7 +14,7 @@ import: props: - name: function - type: "*types.Function" + type: "*types.ApigwFunction" fields: [ ID, ref ] # - name: new # type: "*types.Route" @@ -23,7 +23,7 @@ props: # type: "*types.Route" # fields: [ endpoint, ID ] - name: search - type: "*types.FunctionFilter" + type: "*types.ApigwFunctionFilter" fields: [] actions: @@ -59,3 +59,23 @@ errors: - error: invalidRoute message: "invalid route" severity: warning + + - error: notAllowedToCreate + message: "not allowed to create a function" + log: "failed to create a route; insufficient permissions" + + - error: notAllowedToRead + message: "not allowed to read this function" + log: "failed to read {function}; insufficient permissions" + + - error: notAllowedToUpdate + message: "not allowed to update this function" + log: "failed to update {function}; insufficient permissions" + + - error: notAllowedToDelete + message: "not allowed to delete this function" + log: "failed to delete {function}; insufficient permissions" + + - error: notAllowedToUndelete + message: "not allowed to undelete this function" + log: "failed to undelete {function}; insufficient permissions" diff --git a/system/service/apigw_route.go b/system/service/apigw_route.go new file mode 100644 index 000000000..b2d1093b1 --- /dev/null +++ b/system/service/apigw_route.go @@ -0,0 +1,241 @@ +package service + +import ( + "context" + + "github.com/cortezaproject/corteza-server/pkg/actionlog" + "github.com/cortezaproject/corteza-server/pkg/apigw" + a "github.com/cortezaproject/corteza-server/pkg/auth" + + "github.com/cortezaproject/corteza-server/store" + "github.com/cortezaproject/corteza-server/system/types" +) + +type ( + apigwRoute struct { + actionlog actionlog.Recorder + store store.Storer + ac routeAccessController + } + + routeAccessController interface { + CanGrant(context.Context) bool + CanSearchApiGwRoutes(ctx context.Context) bool + + CanCreateApiGwRoute(context.Context) bool + CanReadApigwRoute(context.Context, *types.ApigwRoute) bool + CanUpdateApigwRoute(context.Context, *types.ApigwRoute) bool + CanDeleteApigwRoute(context.Context, *types.ApigwRoute) bool + } +) + +func Route() *apigwRoute { + return (&apigwRoute{ + ac: DefaultAccessControl, + actionlog: DefaultActionlog, + store: DefaultStore, + }) +} + +func (svc *apigwRoute) FindByID(ctx context.Context, ID uint64) (q *types.ApigwRoute, err error) { + var ( + rProps = &apigwRouteActionProps{} + ) + + err = func() error { + if ID == 0 { + return ApigwRouteErrInvalidID() + } + + if !svc.ac.CanSearchApiGwRoutes(ctx) { + return ApigwRouteErrNotAllowedToRead(rProps) + } + + if q, err = store.LookupApigwRouteByID(ctx, svc.store, ID); err != nil { + return ApigwRouteErrInvalidID().Wrap(err) + } + + rProps.setRoute(q) + + if !svc.ac.CanReadApigwRoute(ctx, q) { + return ApigwRouteErrNotAllowedToRead(rProps) + } + + return nil + }() + + return q, svc.recordAction(ctx, rProps, ApigwRouteActionLookup, err) +} + +func (svc *apigwRoute) Create(ctx context.Context, new *types.ApigwRoute) (q *types.ApigwRoute, err error) { + var ( + qProps = &apigwRouteActionProps{new: new} + ) + + err = func() (err error) { + if !svc.ac.CanCreateApiGwRoute(ctx) { + return ApigwRouteErrNotAllowedToCreate(qProps) + } + + new.ID = nextID() + new.CreatedAt = *now() + new.CreatedBy = a.GetIdentityFromContext(ctx).Identity() + + // todo + new.Group = 0 + + if err = store.CreateApigwRoute(ctx, svc.store, new); err != nil { + return err + } + + q = new + + // send the signal to reload all routes + if new.Enabled { + apigw.Service().Reload(ctx) + } + + return nil + }() + + return q, svc.recordAction(ctx, qProps, ApigwRouteActionCreate, err) +} + +func (svc *apigwRoute) Update(ctx context.Context, upd *types.ApigwRoute) (q *types.ApigwRoute, err error) { + var ( + qProps = &apigwRouteActionProps{update: upd} + qq *types.ApigwRoute + e error + ) + + err = func() (err error) { + if qq, e = store.LookupApigwRouteByID(ctx, svc.store, upd.ID); e != nil { + return ApigwRouteErrNotFound(qProps) + } + + if !svc.ac.CanUpdateApigwRoute(ctx, qq) { + return ApigwRouteErrNotAllowedToUpdate(qProps) + } + + // temp todo - update itself with the same endpoint + // if qq, e = store.LookupApigwRouteByEndpoint(ctx, svc.store, upd.Endpoint); e == nil && qq == nil { + // return ApigwRouteErrExistsEndpoint(qProps) + // } + + upd.UpdatedAt = now() + upd.CreatedAt = qq.CreatedAt + upd.UpdatedBy = a.GetIdentityFromContext(ctx).Identity() + + if err = store.UpdateApigwRoute(ctx, svc.store, upd); err != nil { + return + } + + q = upd + + // send the signal to reload all route + apigw.Service().Reload(ctx) + + return nil + }() + + return q, svc.recordAction(ctx, qProps, ApigwRouteActionUpdate, err) +} + +func (svc *apigwRoute) DeleteByID(ctx context.Context, ID uint64) (err error) { + var ( + qProps = &apigwRouteActionProps{} + q *types.ApigwRoute + ) + + err = func() (err error) { + if ID == 0 { + return ApigwRouteErrInvalidID() + } + + if q, err = store.LookupApigwRouteByID(ctx, svc.store, ID); err != nil { + return + } + + if !svc.ac.CanDeleteApigwRoute(ctx, q) { + return ApigwRouteErrNotAllowedToDelete(qProps) + } + + qProps.setRoute(q) + + q.DeletedAt = now() + q.DeletedBy = a.GetIdentityFromContext(ctx).Identity() + + if err = store.UpdateApigwRoute(ctx, svc.store, q); err != nil { + return + } + + // send the signal to reload all queues + apigw.Service().Reload(ctx) + + return nil + }() + + return svc.recordAction(ctx, qProps, ApigwRouteActionDelete, err) +} + +func (svc *apigwRoute) UndeleteByID(ctx context.Context, ID uint64) (err error) { + var ( + qProps = &apigwRouteActionProps{} + q *types.ApigwRoute + ) + + err = func() (err error) { + if ID == 0 { + return ApigwRouteErrInvalidID() + } + + if q, err = store.LookupApigwRouteByID(ctx, svc.store, ID); err != nil { + return + } + + if !svc.ac.CanDeleteApigwRoute(ctx, q) { + return ApigwRouteErrNotAllowedToDelete(qProps) + } + + qProps.setRoute(q) + + q.DeletedAt = nil + q.UpdatedBy = a.GetIdentityFromContext(ctx).Identity() + + if err = store.UpdateApigwRoute(ctx, svc.store, q); err != nil { + return + } + + // send the signal to reload all queues + apigw.Service().Reload(ctx) + + return nil + }() + + return svc.recordAction(ctx, qProps, ApigwRouteActionDelete, err) +} + +func (svc *apigwRoute) Search(ctx context.Context, filter types.ApigwRouteFilter) (r types.ApigwRouteSet, f types.ApigwRouteFilter, err error) { + var ( + aProps = &apigwRouteActionProps{search: &filter} + ) + + // For each fetched item, store backend will check if it is valid or not + filter.Check = func(res *types.ApigwRoute) (bool, error) { + if !svc.ac.CanReadApigwRoute(ctx, res) { + return false, nil + } + + return true, nil + } + + err = func() error { + if r, f, err = store.SearchApigwRoutes(ctx, svc.store, filter); err != nil { + return err + } + + return nil + }() + + return r, f, svc.recordAction(ctx, aProps, ApigwRouteActionSearch, err) +} diff --git a/system/service/route_actions.gen.go b/system/service/apigw_route_actions.gen.go similarity index 65% rename from system/service/route_actions.gen.go rename to system/service/apigw_route_actions.gen.go index ee466504b..8be6ff46e 100644 --- a/system/service/route_actions.gen.go +++ b/system/service/apigw_route_actions.gen.go @@ -6,7 +6,7 @@ package service // the code is regenerated. // // Definitions file that controls how this file is generated: -// system/service/route_actions.yaml +// system/service/apigw_route_actions.yaml import ( "context" @@ -19,14 +19,14 @@ import ( ) type ( - routeActionProps struct { - route *types.Route - new *types.Route - update *types.Route - search *types.RouteFilter + apigwRouteActionProps struct { + route *types.ApigwRoute + new *types.ApigwRoute + update *types.ApigwRoute + search *types.ApigwRouteFilter } - routeAction struct { + apigwRouteAction struct { timestamp time.Time resource string action string @@ -36,11 +36,11 @@ type ( // prefix for error when action fails errorMessage string - props *routeActionProps + props *apigwRouteActionProps } - routeLogMetaKey struct{} - routePropsMetaKey struct{} + apigwRouteLogMetaKey struct{} + apigwRoutePropsMetaKey struct{} ) var ( @@ -51,55 +51,55 @@ var ( // ********************************************************************************************************************* // ********************************************************************************************************************* // Props methods -// setRoute updates routeActionProps's route +// setRoute updates apigwRouteActionProps's route // // Allows method chaining // // This function is auto-generated. // -func (p *routeActionProps) setRoute(route *types.Route) *routeActionProps { +func (p *apigwRouteActionProps) setRoute(route *types.ApigwRoute) *apigwRouteActionProps { p.route = route return p } -// setNew updates routeActionProps's new +// setNew updates apigwRouteActionProps's new // // Allows method chaining // // This function is auto-generated. // -func (p *routeActionProps) setNew(new *types.Route) *routeActionProps { +func (p *apigwRouteActionProps) setNew(new *types.ApigwRoute) *apigwRouteActionProps { p.new = new return p } -// setUpdate updates routeActionProps's update +// setUpdate updates apigwRouteActionProps's update // // Allows method chaining // // This function is auto-generated. // -func (p *routeActionProps) setUpdate(update *types.Route) *routeActionProps { +func (p *apigwRouteActionProps) setUpdate(update *types.ApigwRoute) *apigwRouteActionProps { p.update = update return p } -// setSearch updates routeActionProps's search +// setSearch updates apigwRouteActionProps's search // // Allows method chaining // // This function is auto-generated. // -func (p *routeActionProps) setSearch(search *types.RouteFilter) *routeActionProps { +func (p *apigwRouteActionProps) setSearch(search *types.ApigwRouteFilter) *apigwRouteActionProps { p.search = search return p } -// Serialize converts routeActionProps to actionlog.Meta +// Serialize converts apigwRouteActionProps to actionlog.Meta // // This function is auto-generated. // -func (p routeActionProps) Serialize() actionlog.Meta { +func (p apigwRouteActionProps) Serialize() actionlog.Meta { var ( m = make(actionlog.Meta) ) @@ -126,7 +126,7 @@ func (p routeActionProps) Serialize() actionlog.Meta { // // This function is auto-generated. // -func (p routeActionProps) Format(in string, err error) string { +func (p apigwRouteActionProps) Format(in string, err error) string { var ( pairs = []string{"{err}"} // first non-empty string @@ -208,8 +208,8 @@ func (p routeActionProps) Format(in string, err error) string { // // This function is auto-generated. // -func (a *routeAction) String() string { - var props = &routeActionProps{} +func (a *apigwRouteAction) String() string { + var props = &apigwRouteActionProps{} if a.props != nil { props = a.props @@ -218,7 +218,7 @@ func (a *routeAction) String() string { return props.Format(a.log, nil) } -func (e *routeAction) ToAction() *actionlog.Action { +func (e *apigwRouteAction) ToAction() *actionlog.Action { return &actionlog.Action{ Resource: e.resource, Action: e.action, @@ -232,12 +232,12 @@ func (e *routeAction) ToAction() *actionlog.Action { // ********************************************************************************************************************* // Action constructors -// RouteActionSearch returns "system:route.search" action +// ApigwRouteActionSearch returns "system:route.search" action // // This function is auto-generated. // -func RouteActionSearch(props ...*routeActionProps) *routeAction { - a := &routeAction{ +func ApigwRouteActionSearch(props ...*apigwRouteActionProps) *apigwRouteAction { + a := &apigwRouteAction{ timestamp: time.Now(), resource: "system:route", action: "search", @@ -252,12 +252,12 @@ func RouteActionSearch(props ...*routeActionProps) *routeAction { return a } -// RouteActionLookup returns "system:route.lookup" action +// ApigwRouteActionLookup returns "system:route.lookup" action // // This function is auto-generated. // -func RouteActionLookup(props ...*routeActionProps) *routeAction { - a := &routeAction{ +func ApigwRouteActionLookup(props ...*apigwRouteActionProps) *apigwRouteAction { + a := &apigwRouteAction{ timestamp: time.Now(), resource: "system:route", action: "lookup", @@ -272,12 +272,12 @@ func RouteActionLookup(props ...*routeActionProps) *routeAction { return a } -// RouteActionCreate returns "system:route.create" action +// ApigwRouteActionCreate returns "system:route.create" action // // This function is auto-generated. // -func RouteActionCreate(props ...*routeActionProps) *routeAction { - a := &routeAction{ +func ApigwRouteActionCreate(props ...*apigwRouteActionProps) *apigwRouteAction { + a := &apigwRouteAction{ timestamp: time.Now(), resource: "system:route", action: "create", @@ -292,12 +292,12 @@ func RouteActionCreate(props ...*routeActionProps) *routeAction { return a } -// RouteActionUpdate returns "system:route.update" action +// ApigwRouteActionUpdate returns "system:route.update" action // // This function is auto-generated. // -func RouteActionUpdate(props ...*routeActionProps) *routeAction { - a := &routeAction{ +func ApigwRouteActionUpdate(props ...*apigwRouteActionProps) *apigwRouteAction { + a := &apigwRouteAction{ timestamp: time.Now(), resource: "system:route", action: "update", @@ -312,12 +312,12 @@ func RouteActionUpdate(props ...*routeActionProps) *routeAction { return a } -// RouteActionDelete returns "system:route.delete" action +// ApigwRouteActionDelete returns "system:route.delete" action // // This function is auto-generated. // -func RouteActionDelete(props ...*routeActionProps) *routeAction { - a := &routeAction{ +func ApigwRouteActionDelete(props ...*apigwRouteActionProps) *apigwRouteAction { + a := &apigwRouteAction{ timestamp: time.Now(), resource: "system:route", action: "delete", @@ -332,12 +332,12 @@ func RouteActionDelete(props ...*routeActionProps) *routeAction { return a } -// RouteActionUndelete returns "system:route.undelete" action +// ApigwRouteActionUndelete returns "system:route.undelete" action // // This function is auto-generated. // -func RouteActionUndelete(props ...*routeActionProps) *routeAction { - a := &routeAction{ +func ApigwRouteActionUndelete(props ...*apigwRouteActionProps) *apigwRouteAction { + a := &apigwRouteAction{ timestamp: time.Now(), resource: "system:route", action: "undelete", @@ -356,13 +356,13 @@ func RouteActionUndelete(props ...*routeActionProps) *routeAction { // ********************************************************************************************************************* // Error constructors -// RouteErrGeneric returns "system:route.generic" as *errors.Error +// ApigwRouteErrGeneric returns "system:route.generic" as *errors.Error // // // This function is auto-generated. // -func RouteErrGeneric(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrGeneric(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -376,8 +376,8 @@ func RouteErrGeneric(mm ...*routeActionProps) *errors.Error { errors.Meta("resource", "system:route"), // action log entry; no formatting, it will be applied inside recordAction fn. - errors.Meta(routeLogMetaKey{}, "{err}"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRouteLogMetaKey{}, "{err}"), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -388,13 +388,13 @@ func RouteErrGeneric(mm ...*routeActionProps) *errors.Error { return e } -// RouteErrNotFound returns "system:route.notFound" as *errors.Error +// ApigwRouteErrNotFound returns "system:route.notFound" as *errors.Error // // // This function is auto-generated. // -func RouteErrNotFound(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrNotFound(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -407,7 +407,7 @@ func RouteErrNotFound(mm ...*routeActionProps) *errors.Error { errors.Meta("type", "notFound"), errors.Meta("resource", "system:route"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -418,13 +418,13 @@ func RouteErrNotFound(mm ...*routeActionProps) *errors.Error { return e } -// RouteErrInvalidID returns "system:route.invalidID" as *errors.Error +// ApigwRouteErrInvalidID returns "system:route.invalidID" as *errors.Error // // // This function is auto-generated. // -func RouteErrInvalidID(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrInvalidID(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -437,7 +437,7 @@ func RouteErrInvalidID(mm ...*routeActionProps) *errors.Error { errors.Meta("type", "invalidID"), errors.Meta("resource", "system:route"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -448,13 +448,13 @@ func RouteErrInvalidID(mm ...*routeActionProps) *errors.Error { return e } -// RouteErrInvalidEndpoint returns "system:route.invalidEndpoint" as *errors.Error +// ApigwRouteErrInvalidEndpoint returns "system:route.invalidEndpoint" as *errors.Error // // // This function is auto-generated. // -func RouteErrInvalidEndpoint(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrInvalidEndpoint(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -467,7 +467,7 @@ func RouteErrInvalidEndpoint(mm ...*routeActionProps) *errors.Error { errors.Meta("type", "invalidEndpoint"), errors.Meta("resource", "system:route"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -478,13 +478,13 @@ func RouteErrInvalidEndpoint(mm ...*routeActionProps) *errors.Error { return e } -// RouteErrExistsEndpoint returns "system:route.existsEndpoint" as *errors.Error +// ApigwRouteErrExistsEndpoint returns "system:route.existsEndpoint" as *errors.Error // // // This function is auto-generated. // -func RouteErrExistsEndpoint(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrExistsEndpoint(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -497,7 +497,7 @@ func RouteErrExistsEndpoint(mm ...*routeActionProps) *errors.Error { errors.Meta("type", "existsEndpoint"), errors.Meta("resource", "system:route"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -508,13 +508,13 @@ func RouteErrExistsEndpoint(mm ...*routeActionProps) *errors.Error { return e } -// RouteErrAlreadyExists returns "system:route.alreadyExists" as *errors.Error +// ApigwRouteErrAlreadyExists returns "system:route.alreadyExists" as *errors.Error // // // This function is auto-generated. // -func RouteErrAlreadyExists(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrAlreadyExists(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -527,7 +527,7 @@ func RouteErrAlreadyExists(mm ...*routeActionProps) *errors.Error { errors.Meta("type", "alreadyExists"), errors.Meta("resource", "system:route"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -538,13 +538,13 @@ func RouteErrAlreadyExists(mm ...*routeActionProps) *errors.Error { return e } -// RouteErrNotAllowedToCreate returns "system:route.notAllowedToCreate" as *errors.Error +// ApigwRouteErrNotAllowedToCreate returns "system:route.notAllowedToCreate" as *errors.Error // // // This function is auto-generated. // -func RouteErrNotAllowedToCreate(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrNotAllowedToCreate(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -558,8 +558,8 @@ func RouteErrNotAllowedToCreate(mm ...*routeActionProps) *errors.Error { errors.Meta("resource", "system:route"), // action log entry; no formatting, it will be applied inside recordAction fn. - errors.Meta(routeLogMetaKey{}, "failed to create a route; insufficient permissions"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRouteLogMetaKey{}, "failed to create a route; insufficient permissions"), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -570,13 +570,13 @@ func RouteErrNotAllowedToCreate(mm ...*routeActionProps) *errors.Error { return e } -// RouteErrNotAllowedToRead returns "system:route.notAllowedToRead" as *errors.Error +// ApigwRouteErrNotAllowedToRead returns "system:route.notAllowedToRead" as *errors.Error // // // This function is auto-generated. // -func RouteErrNotAllowedToRead(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrNotAllowedToRead(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -590,8 +590,8 @@ func RouteErrNotAllowedToRead(mm ...*routeActionProps) *errors.Error { errors.Meta("resource", "system:route"), // action log entry; no formatting, it will be applied inside recordAction fn. - errors.Meta(routeLogMetaKey{}, "failed to read {route.endpoint}; insufficient permissions"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRouteLogMetaKey{}, "failed to read {route.endpoint}; insufficient permissions"), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -602,13 +602,13 @@ func RouteErrNotAllowedToRead(mm ...*routeActionProps) *errors.Error { return e } -// RouteErrNotAllowedToUpdate returns "system:route.notAllowedToUpdate" as *errors.Error +// ApigwRouteErrNotAllowedToUpdate returns "system:route.notAllowedToUpdate" as *errors.Error // // // This function is auto-generated. // -func RouteErrNotAllowedToUpdate(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrNotAllowedToUpdate(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -622,8 +622,8 @@ func RouteErrNotAllowedToUpdate(mm ...*routeActionProps) *errors.Error { errors.Meta("resource", "system:route"), // action log entry; no formatting, it will be applied inside recordAction fn. - errors.Meta(routeLogMetaKey{}, "failed to update {route.endpoint}; insufficient permissions"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRouteLogMetaKey{}, "failed to update {route.endpoint}; insufficient permissions"), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -634,13 +634,13 @@ func RouteErrNotAllowedToUpdate(mm ...*routeActionProps) *errors.Error { return e } -// RouteErrNotAllowedToDelete returns "system:route.notAllowedToDelete" as *errors.Error +// ApigwRouteErrNotAllowedToDelete returns "system:route.notAllowedToDelete" as *errors.Error // // // This function is auto-generated. // -func RouteErrNotAllowedToDelete(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrNotAllowedToDelete(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -654,8 +654,8 @@ func RouteErrNotAllowedToDelete(mm ...*routeActionProps) *errors.Error { errors.Meta("resource", "system:route"), // action log entry; no formatting, it will be applied inside recordAction fn. - errors.Meta(routeLogMetaKey{}, "failed to delete {route.endpoint}; insufficient permissions"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRouteLogMetaKey{}, "failed to delete {route.endpoint}; insufficient permissions"), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -666,13 +666,13 @@ func RouteErrNotAllowedToDelete(mm ...*routeActionProps) *errors.Error { return e } -// RouteErrNotAllowedToUndelete returns "system:route.notAllowedToUndelete" as *errors.Error +// ApigwRouteErrNotAllowedToUndelete returns "system:route.notAllowedToUndelete" as *errors.Error // // // This function is auto-generated. // -func RouteErrNotAllowedToUndelete(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrNotAllowedToUndelete(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -686,8 +686,8 @@ func RouteErrNotAllowedToUndelete(mm ...*routeActionProps) *errors.Error { errors.Meta("resource", "system:route"), // action log entry; no formatting, it will be applied inside recordAction fn. - errors.Meta(routeLogMetaKey{}, "failed to undelete {route.endpoint}; insufficient permissions"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRouteLogMetaKey{}, "failed to undelete {route.endpoint}; insufficient permissions"), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -698,13 +698,13 @@ func RouteErrNotAllowedToUndelete(mm ...*routeActionProps) *errors.Error { return e } -// RouteErrNotAllowedToExec returns "system:route.notAllowedToExec" as *errors.Error +// ApigwRouteErrNotAllowedToExec returns "system:route.notAllowedToExec" as *errors.Error // // // This function is auto-generated. // -func RouteErrNotAllowedToExec(mm ...*routeActionProps) *errors.Error { - var p = &routeActionProps{} +func ApigwRouteErrNotAllowedToExec(mm ...*apigwRouteActionProps) *errors.Error { + var p = &apigwRouteActionProps{} if len(mm) > 0 { p = mm[0] } @@ -718,8 +718,8 @@ func RouteErrNotAllowedToExec(mm ...*routeActionProps) *errors.Error { errors.Meta("resource", "system:route"), // action log entry; no formatting, it will be applied inside recordAction fn. - errors.Meta(routeLogMetaKey{}, "failed to exec {route.endpoint}; insufficient permissions"), - errors.Meta(routePropsMetaKey{}, p), + errors.Meta(apigwRouteLogMetaKey{}, "failed to exec {route.endpoint}; insufficient permissions"), + errors.Meta(apigwRoutePropsMetaKey{}, p), errors.StackSkip(1), ) @@ -739,7 +739,7 @@ func RouteErrNotAllowedToExec(mm ...*routeActionProps) *errors.Error { // // This function is auto-generated. // -func (svc route) recordAction(ctx context.Context, props *routeActionProps, actionFn func(...*routeActionProps) *routeAction, err error) error { +func (svc apigwRoute) recordAction(ctx context.Context, props *apigwRouteActionProps, actionFn func(...*apigwRouteActionProps) *apigwRouteAction, err error) error { if svc.actionlog == nil || actionFn == nil { // action log disabled or no action fn passed, return error as-is return err @@ -760,10 +760,10 @@ func (svc route) recordAction(ctx context.Context, props *routeActionProps, acti a.Error = err.Error() a.Severity = actionlog.Severity(m.AsInt("severity")) - a.Description = props.Format(m.AsString(routeLogMetaKey{}), err) + a.Description = props.Format(m.AsString(apigwRouteLogMetaKey{}), err) - if p, has := m[routePropsMetaKey{}]; has { - a.Meta = p.(*routeActionProps).Serialize() + if p, has := m[apigwRoutePropsMetaKey{}]; has { + a.Meta = p.(*apigwRouteActionProps).Serialize() } svc.actionlog.Record(ctx, a) diff --git a/system/service/route_actions.yaml b/system/service/apigw_route_actions.yaml similarity index 93% rename from system/service/route_actions.yaml rename to system/service/apigw_route_actions.yaml index 1be4ece87..f6355afe2 100644 --- a/system/service/route_actions.yaml +++ b/system/service/apigw_route_actions.yaml @@ -1,7 +1,7 @@ # List of loggable service actions resource: system:route -service: route +service: apigwRoute # Default sensitivity for actions defaultActionSeverity: notice @@ -14,16 +14,16 @@ import: props: - name: route - type: "*types.Route" + type: "*types.ApigwRoute" fields: [ endpoint, ID ] - name: new - type: "*types.Route" + type: "*types.ApigwRoute" fields: [ endpoint, ID ] - name: update - type: "*types.Route" + type: "*types.ApigwRoute" fields: [ endpoint, ID ] - name: search - type: "*types.RouteFilter" + type: "*types.ApigwRouteFilter" fields: [] actions: diff --git a/system/service/function.go b/system/service/function.go deleted file mode 100644 index d3a2c408e..000000000 --- a/system/service/function.go +++ /dev/null @@ -1,229 +0,0 @@ -package service - -import ( - "context" - - "github.com/cortezaproject/corteza-server/pkg/actionlog" - "github.com/cortezaproject/corteza-server/pkg/apigw" - a "github.com/cortezaproject/corteza-server/pkg/auth" - "github.com/cortezaproject/corteza-server/store" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - function struct { - actionlog actionlog.Recorder - store store.Storer - ac functionAccessController - } - - functionAccessController interface{} -) - -func Function() *function { - return (&function{ - ac: DefaultAccessControl, - actionlog: DefaultActionlog, - store: DefaultStore, - }) -} - -func (svc *function) FindByID(ctx context.Context, ID uint64) (q *types.Function, err error) { - var ( - rProps = &functionActionProps{} - ) - - err = func() error { - if ID == 0 { - return FunctionErrInvalidID() - } - - if q, err = store.LookupApigwFunctionByID(ctx, svc.store, ID); err != nil { - return TemplateErrInvalidID().Wrap(err) - } - - rProps.setFunction(q) - - // if !svc.ac.CanReadMessagebusQueue(ctx, q) { - // return QueueErrNotAllowedToRead(qProps) - // } - - return nil - }() - - return q, svc.recordAction(ctx, rProps, FunctionActionLookup, err) -} - -func (svc *function) Create(ctx context.Context, new *types.Function) (q *types.Function, err error) { - var ( - qProps = &functionActionProps{function: new} - ) - - err = func() (err error) { - // if !svc.ac.CanCreateMessagebusQueue(ctx) { - // return QueueErrNotAllowedToCreate(qProps) - // } - - // Set new values after beforeCreate events are emitted - new.ID = nextID() - new.CreatedAt = *now() - new.CreatedBy = a.GetIdentityFromContext(ctx).Identity() - - if _, err = DefaultRoute.FindByID(ctx, new.Route); err != nil { - return FunctionErrInvalidRoute(qProps) - } - - if err = store.CreateApigwFunction(ctx, svc.store, new); err != nil { - return err - } - - q = new - // send the signal to reload all functions - // apigw.Service().Reload(ctx) - // } - - return nil - }() - - return q, svc.recordAction(ctx, qProps, FunctionActionCreate, err) -} - -func (svc *function) Update(ctx context.Context, upd *types.Function) (q *types.Function, err error) { - var ( - qProps = &functionActionProps{function: upd} - qq *types.Function - e error - ) - - err = func() (err error) { - // if !svc.ac.CanUpdateMessagebusQueue(ctx, upd) { - // return QueueErrNotAllowedToUpdate(qProps) - // } - - if qq, e = store.LookupApigwFunctionByID(ctx, svc.store, upd.ID); e != nil { - return FunctionErrNotFound(qProps) - } - - if _, err = DefaultRoute.FindByID(ctx, upd.Route); err != nil { - return FunctionErrInvalidRoute(qProps) - } - - if qq, e = store.LookupApigwFunctionByID(ctx, svc.store, upd.ID); e == nil && qq == nil { - return FunctionErrNotFound(qProps) - } - - // Set new values after beforeCreate events are emitted - upd.UpdatedAt = now() - upd.CreatedAt = qq.CreatedAt - upd.UpdatedBy = a.GetIdentityFromContext(ctx).Identity() - - if err = store.UpdateApigwFunction(ctx, svc.store, upd); err != nil { - return - } - - q = upd - - // send the signal to reload all function - // apigw.Service().Reload(ctx) - - return nil - }() - - return q, svc.recordAction(ctx, qProps, FunctionActionUpdate, err) -} - -func (svc *function) DeleteByID(ctx context.Context, ID uint64) (err error) { - var ( - qProps = &functionActionProps{} - q *types.Function - ) - - err = func() (err error) { - if q, err = store.LookupApigwFunctionByID(ctx, svc.store, ID); err != nil { - return FunctionErrNotFound(qProps) - } - - qProps.setFunction(q) - - // if !svc.ac.CanDeleteMessagebusQueue(ctx, q) { - // return QueueErrNotAllowedToDelete(qProps) - // } - - q.DeletedAt = now() - q.DeletedBy = a.GetIdentityFromContext(ctx).Identity() - - if err = store.UpdateApigwFunction(ctx, svc.store, q); err != nil { - return - } - - // send the signal to reload all queues - // apigw.Service().Reload(ctx) - - return nil - }() - - return svc.recordAction(ctx, qProps, FunctionActionDelete, err) -} - -func (svc *function) UndeleteByID(ctx context.Context, ID uint64) (err error) { - var ( - qProps = &functionActionProps{} - q *types.Function - ) - - err = func() (err error) { - if q, err = store.LookupApigwFunctionByID(ctx, svc.store, ID); err != nil { - return FunctionErrNotFound(qProps) - } - - qProps.setFunction(q) - - // if !svc.ac.CanDeleteMessagebusQueue(ctx, q) { - // return QueueErrNotAllowedToDelete(qProps) - // } - - q.DeletedAt = nil - q.UpdatedBy = a.GetIdentityFromContext(ctx).Identity() - - if err = store.UpdateApigwFunction(ctx, svc.store, q); err != nil { - return - } - - // send the signal to reload all queues - // apigw.Service().Reload(ctx) - - return nil - }() - - return svc.recordAction(ctx, qProps, FunctionActionDelete, err) -} - -func (svc *function) Search(ctx context.Context, filter types.FunctionFilter) (r types.FunctionSet, f types.FunctionFilter, err error) { - var ( - aProps = &functionActionProps{search: &filter} - ) - - // For each fetched item, store backend will check if it is valid or not - // filter.Check = func(res *messagebus.QueueSettings) (bool, error) { - // if !svc.ac.CanReadMessagebusQueue(ctx, res) { - // return false, nil - // } - - // return true, nil - // } - - err = func() error { - if r, f, err = store.SearchApigwFunctions(ctx, svc.store, filter); err != nil { - return err - } - - return nil - }() - - return r, f, svc.recordAction(ctx, aProps, FunctionActionSearch, err) -} - -func (svc *function) Definitions(ctx context.Context, kind string) (interface{}, error) { - // get the definitions from registry - return apigw.Service().Funcs(kind), nil -} diff --git a/system/service/route.go b/system/service/route.go deleted file mode 100644 index 43d22ec32..000000000 --- a/system/service/route.go +++ /dev/null @@ -1,230 +0,0 @@ -package service - -import ( - "context" - - "github.com/cortezaproject/corteza-server/pkg/actionlog" - a "github.com/cortezaproject/corteza-server/pkg/auth" - "github.com/cortezaproject/corteza-server/store" - "github.com/cortezaproject/corteza-server/system/types" -) - -type ( - route struct { - actionlog actionlog.Recorder - store store.Storer - ac routeAccessController - } - - routeAccessController interface { - } -) - -func Route() *route { - return (&route{ - ac: DefaultAccessControl, - actionlog: DefaultActionlog, - store: DefaultStore, - }) -} - -func (svc *route) FindByID(ctx context.Context, ID uint64) (q *types.Route, err error) { - var ( - rProps = &routeActionProps{} - ) - - err = func() error { - if ID == 0 { - return RouteErrInvalidID() - } - - if q, err = store.LookupApigwRouteByID(ctx, svc.store, ID); err != nil { - return RouteErrInvalidID().Wrap(err) - } - - rProps.setRoute(q) - - // if !svc.ac.CanReadMessagebusQueue(ctx, q) { - // return QueueErrNotAllowedToRead(qProps) - // } - - return nil - }() - - return q, svc.recordAction(ctx, rProps, RouteActionLookup, err) -} - -func (svc *route) Create(ctx context.Context, new *types.Route) (q *types.Route, err error) { - var ( - qProps = &routeActionProps{new: new} - ) - - err = func() (err error) { - // if !svc.ac.CanCreateMessagebusQueue(ctx) { - // return QueueErrNotAllowedToCreate(qProps) - // } - - // Set new values after beforeCreate events are emitted - new.ID = nextID() - new.CreatedAt = *now() - new.CreatedBy = a.GetIdentityFromContext(ctx).Identity() - - // todo - new.Group = 0 - - if err = store.CreateApigwRoute(ctx, svc.store, new); err != nil { - return err - } - - q = new - - // send the signal to reload all routes - // if new.Enabled { - // apigw.Service().Reload(ctx) - // } - - return nil - }() - - return q, svc.recordAction(ctx, qProps, RouteActionCreate, err) -} - -func (svc *route) Update(ctx context.Context, upd *types.Route) (q *types.Route, err error) { - var ( - qProps = &routeActionProps{update: upd} - qq *types.Route - e error - ) - - err = func() (err error) { - // if !svc.ac.CanUpdateMessagebusQueue(ctx, upd) { - // return QueueErrNotAllowedToUpdate(qProps) - // } - - if qq, e = store.LookupApigwRouteByID(ctx, svc.store, upd.ID); e != nil { - return RouteErrNotFound(qProps) - } - - // temp todo - update itself with the same endpoint - // if qq, e = store.LookupApigwRouteByEndpoint(ctx, svc.store, upd.Endpoint); e == nil && qq == nil { - // return RouteErrExistsEndpoint(qProps) - // } - - // Set new values after beforeCreate events are emitted - upd.UpdatedAt = now() - upd.CreatedAt = qq.CreatedAt - upd.UpdatedBy = a.GetIdentityFromContext(ctx).Identity() - - if err = store.UpdateApigwRoute(ctx, svc.store, upd); err != nil { - return - } - - q = upd - - // send the signal to reload all route - // apigw.Service().Reload(ctx) - - return nil - }() - - return q, svc.recordAction(ctx, qProps, RouteActionUpdate, err) -} - -func (svc *route) DeleteByID(ctx context.Context, ID uint64) (err error) { - var ( - qProps = &routeActionProps{} - q *types.Route - ) - - err = func() (err error) { - if ID == 0 { - return RouteErrInvalidID() - } - - if q, err = store.LookupApigwRouteByID(ctx, svc.store, ID); err != nil { - return - } - - qProps.setRoute(q) - - // if !svc.ac.CanDeleteMessagebusQueue(ctx, q) { - // return QueueErrNotAllowedToDelete(qProps) - // } - - q.DeletedAt = now() - q.DeletedBy = a.GetIdentityFromContext(ctx).Identity() - - if err = store.UpdateApigwRoute(ctx, svc.store, q); err != nil { - return - } - - // send the signal to reload all queues - // apigw.Service().Reload(ctx) - - return nil - }() - - return svc.recordAction(ctx, qProps, RouteActionDelete, err) -} - -func (svc *route) UndeleteByID(ctx context.Context, ID uint64) (err error) { - var ( - qProps = &routeActionProps{} - q *types.Route - ) - - err = func() (err error) { - if ID == 0 { - return RouteErrInvalidID() - } - - if q, err = store.LookupApigwRouteByID(ctx, svc.store, ID); err != nil { - return - } - - qProps.setRoute(q) - - // if !svc.ac.CanDeleteMessagebusQueue(ctx, q) { - // return QueueErrNotAllowedToDelete(qProps) - // } - - q.DeletedAt = nil - q.UpdatedBy = a.GetIdentityFromContext(ctx).Identity() - - if err = store.UpdateApigwRoute(ctx, svc.store, q); err != nil { - return - } - - // send the signal to reload all queues - // apigw.Service().Reload(ctx) - - return nil - }() - - return svc.recordAction(ctx, qProps, RouteActionDelete, err) -} - -func (svc *route) Search(ctx context.Context, filter types.RouteFilter) (r types.RouteSet, f types.RouteFilter, err error) { - var ( - aProps = &routeActionProps{search: &filter} - ) - - // For each fetched item, store backend will check if it is valid or not - // filter.Check = func(res *messagebus.QueueSettings) (bool, error) { - // if !svc.ac.CanReadMessagebusQueue(ctx, res) { - // return false, nil - // } - - // return true, nil - // } - - err = func() error { - if r, f, err = store.SearchApigwRoutes(ctx, svc.store, filter); err != nil { - return err - } - - return nil - }() - - return r, f, svc.recordAction(ctx, aProps, RouteActionSearch, err) -} diff --git a/system/service/service.go b/system/service/service.go index 8409f492c..345a2cc2c 100644 --- a/system/service/service.go +++ b/system/service/service.go @@ -75,8 +75,8 @@ var ( DefaultAttachment AttachmentService DefaultRenderer TemplateService DefaultQueue *queue - DefaultRoute *route - DefaultFunction *function + DefaultRoute *apigwRoute + DefaultFunction *apigwFunction DefaultStatistics *statistics diff --git a/system/types/function.go b/system/types/apigw_function.go similarity index 56% rename from system/types/function.go rename to system/types/apigw_function.go index 41e6feb68..631af195b 100644 --- a/system/types/function.go +++ b/system/types/apigw_function.go @@ -10,17 +10,15 @@ import ( ) type ( - ApigwFunctionKind string + ApigwFuncParams map[string]interface{} - FuncParams map[string]interface{} - - Function struct { - ID uint64 `json:"functionID,string"` - Route uint64 `json:"routeID,string"` - Weight uint64 `json:"weight"` - Ref string `json:"ref,omitempty"` - Kind string `json:"kind,omitempty"` - Params FuncParams `json:"params"` + ApigwFunction struct { + ID uint64 `json:"functionID,string"` + Route uint64 `json:"routeID,string"` + Weight uint64 `json:"weight,string"` + Ref string `json:"ref,omitempty"` + Kind string `json:"kind,omitempty"` + Params ApigwFuncParams `json:"params"` CreatedAt time.Time `json:"createdAt,omitempty"` CreatedBy uint64 `json:"createdBy,string" ` @@ -30,7 +28,7 @@ type ( DeletedBy uint64 `json:"deletedBy,string,omitempty" ` } - FunctionFilter struct { + ApigwFunctionFilter struct { RouteID uint64 `json:"routeID,string"` Endpoint string `json:"endpoint"` Group string `json:"group"` @@ -42,22 +40,14 @@ type ( // modify the resource and return false if store should not return it // // Store then loads additional resources to satisfy the paging parameters - Check func(*Route) (bool, error) `json:"-"` + Check func(*ApigwFunction) (bool, error) `json:"-"` filter.Sorting filter.Paging } ) -const ( - ApigwFunctionKindVerifier ApigwFunctionKind = "functionVerifier" - ApigwFunctionKindValidator ApigwFunctionKind = "functionValidator" - ApigwFunctionKindMatcher ApigwFunctionKind = "functionMatcher" - ApigwFunctionKindProcesser ApigwFunctionKind = "functionProcesser" - ApigwFunctionKindExpediter ApigwFunctionKind = "functionExpediter" -) - -func (vv *FuncParams) Scan(value interface{}) (err error) { +func (vv *ApigwFuncParams) Scan(value interface{}) (err error) { if err := json.Unmarshal(value.([]byte), vv); err != nil { return fmt.Errorf("cannot scan '%v' into FuncParams", value) } @@ -65,6 +55,6 @@ func (vv *FuncParams) Scan(value interface{}) (err error) { return } -func (vv FuncParams) Value() (driver.Value, error) { +func (vv ApigwFuncParams) Value() (driver.Value, error) { return json.Marshal(vv) } diff --git a/system/types/route.go b/system/types/apigw_route.go similarity index 87% rename from system/types/route.go rename to system/types/apigw_route.go index fc6e61251..813d1fcd8 100644 --- a/system/types/route.go +++ b/system/types/apigw_route.go @@ -7,15 +7,13 @@ import ( ) type ( - RouteMeta struct{} - - Route struct { + ApigwRoute struct { ID uint64 `json:"routeID,string"` Endpoint string `json:"endpoint"` Method string `json:"method"` Debug bool `json:"debug"` Enabled bool `json:"enabled"` - Group uint64 `json:"group"` + Group uint64 `json:"group,string"` CreatedAt time.Time `json:"createdAt,omitempty"` CreatedBy uint64 `json:"createdBy,string" ` @@ -25,7 +23,7 @@ type ( DeletedBy uint64 `json:"deletedBy,string,omitempty" ` } - RouteFilter struct { + ApigwRouteFilter struct { Route string `json:"route"` Group string `json:"group"` Enabled bool `json:"enabled"` @@ -36,7 +34,7 @@ type ( // modify the resource and return false if store should not return it // // Store then loads additional resources to satisfy the paging parameters - Check func(*Route) (bool, error) `json:"-"` + Check func(*ApigwRoute) (bool, error) `json:"-"` filter.Sorting filter.Paging diff --git a/system/types/parsers.go b/system/types/parsers.go index 2cf2d20ec..12e035262 100644 --- a/system/types/parsers.go +++ b/system/types/parsers.go @@ -4,8 +4,8 @@ import ( "encoding/json" ) -func ParseApigwfFunctionParams(ss []string) (p FuncParams, err error) { - p = FuncParams{} +func ParseApigwfFunctionParams(ss []string) (p ApigwFuncParams, err error) { + p = ApigwFuncParams{} return p, parseStringsInput(ss, p) } diff --git a/system/types/rbac.gen.go b/system/types/rbac.gen.go index fcfbac814..3f2c69979 100644 --- a/system/types/rbac.gen.go +++ b/system/types/rbac.gen.go @@ -7,10 +7,11 @@ package types // // Definitions file that controls how this file is generated: +// - system.apigw-function.yaml +// - system.apigw-route.yaml // - system.application.yaml // - system.auth-client.yaml // - system.role.yaml -// - system.route.yaml // - system.template.yaml // - system.user.yaml // - system.yaml @@ -28,15 +29,78 @@ type ( ) const ( - ApplicationResourceType = "corteza::system:application" - AuthClientResourceType = "corteza::system:auth-client" - RoleResourceType = "corteza::system:role" - RouteResourceType = "corteza::system:route" - TemplateResourceType = "corteza::system:template" - UserResourceType = "corteza::system:user" - ComponentResourceType = "corteza::system" + ApigwFunctionResourceType = "corteza::system:apigw-function" + ApigwRouteResourceType = "corteza::system:apigw-route" + ApplicationResourceType = "corteza::system:application" + AuthClientResourceType = "corteza::system:auth-client" + RoleResourceType = "corteza::system:role" + TemplateResourceType = "corteza::system:template" + UserResourceType = "corteza::system:user" + ComponentResourceType = "corteza::system" ) +// RbacResource returns string representation of RBAC resource for ApigwFunction by calling ApigwFunctionRbacResource fn +// +// RBAC resource is in the corteza::system:apigw-function/... format +// +// This function is auto-generated +func (r ApigwFunction) RbacResource() string { + return ApigwFunctionRbacResource(r.ID) +} + +// ApigwFunctionRbacResource returns string representation of RBAC resource for ApigwFunction +// +// RBAC resource is in the corteza::system:apigw-function/... format +// +// This function is auto-generated +func ApigwFunctionRbacResource(id uint64) string { + cpts := []interface{}{ApigwFunctionResourceType} + if id != 0 { + cpts = append(cpts, strconv.FormatUint(id, 10)) + } else { + cpts = append(cpts, "*") + } + + return fmt.Sprintf(ApigwFunctionRbacResourceTpl(), cpts...) + +} + +// @todo template +func ApigwFunctionRbacResourceTpl() string { + return "%s/%s" +} + +// RbacResource returns string representation of RBAC resource for ApigwRoute by calling ApigwRouteRbacResource fn +// +// RBAC resource is in the corteza::system:apigw-route/... format +// +// This function is auto-generated +func (r ApigwRoute) RbacResource() string { + return ApigwRouteRbacResource(r.ID) +} + +// ApigwRouteRbacResource returns string representation of RBAC resource for ApigwRoute +// +// RBAC resource is in the corteza::system:apigw-route/... format +// +// This function is auto-generated +func ApigwRouteRbacResource(id uint64) string { + cpts := []interface{}{ApigwRouteResourceType} + if id != 0 { + cpts = append(cpts, strconv.FormatUint(id, 10)) + } else { + cpts = append(cpts, "*") + } + + return fmt.Sprintf(ApigwRouteRbacResourceTpl(), cpts...) + +} + +// @todo template +func ApigwRouteRbacResourceTpl() string { + return "%s/%s" +} + // RbacResource returns string representation of RBAC resource for Application by calling ApplicationRbacResource fn // // RBAC resource is in the corteza::system:application/... format @@ -130,37 +194,6 @@ func RoleRbacResourceTpl() string { return "%s/%s" } -// RbacResource returns string representation of RBAC resource for Route by calling RouteRbacResource fn -// -// RBAC resource is in the corteza::system:route/... format -// -// This function is auto-generated -func (r Route) RbacResource() string { - return RouteRbacResource(r.ID) -} - -// RouteRbacResource returns string representation of RBAC resource for Route -// -// RBAC resource is in the corteza::system:route/... format -// -// This function is auto-generated -func RouteRbacResource(id uint64) string { - cpts := []interface{}{RouteResourceType} - if id != 0 { - cpts = append(cpts, strconv.FormatUint(id, 10)) - } else { - cpts = append(cpts, "*") - } - - return fmt.Sprintf(RouteRbacResourceTpl(), cpts...) - -} - -// @todo template -func RouteRbacResourceTpl() string { - return "%s/%s" -} - // RbacResource returns string representation of RBAC resource for Template by calling TemplateRbacResource fn // // RBAC resource is in the corteza::system:template/... format diff --git a/system/types/type_set.gen.go b/system/types/type_set.gen.go index 05674f883..15ede3f31 100644 --- a/system/types/type_set.gen.go +++ b/system/types/type_set.gen.go @@ -10,6 +10,16 @@ package types type ( + // ApigwFunctionSet slice of ApigwFunction + // + // This type is auto-generated. + ApigwFunctionSet []*ApigwFunction + + // ApigwRouteSet slice of ApigwRoute + // + // This type is auto-generated. + ApigwRouteSet []*ApigwRoute + // ApplicationSet slice of Application // // This type is auto-generated. @@ -45,11 +55,6 @@ type ( // This type is auto-generated. CredentialsSet []*Credentials - // FunctionSet slice of Function - // - // This type is auto-generated. - FunctionSet []*Function - // ReminderSet slice of Reminder // // This type is auto-generated. @@ -65,11 +70,6 @@ type ( // This type is auto-generated. RoleMemberSet []*RoleMember - // RouteSet slice of Route - // - // This type is auto-generated. - RouteSet []*Route - // SettingValueSet slice of SettingValue // // This type is auto-generated. @@ -86,6 +86,118 @@ type ( UserSet []*User ) +// Walk iterates through every slice item and calls w(ApigwFunction) err +// +// This function is auto-generated. +func (set ApigwFunctionSet) Walk(w func(*ApigwFunction) error) (err error) { + for i := range set { + if err = w(set[i]); err != nil { + return + } + } + + return +} + +// Filter iterates through every slice item, calls f(ApigwFunction) (bool, err) and return filtered slice +// +// This function is auto-generated. +func (set ApigwFunctionSet) Filter(f func(*ApigwFunction) (bool, error)) (out ApigwFunctionSet, err error) { + var ok bool + out = ApigwFunctionSet{} + for i := range set { + if ok, err = f(set[i]); err != nil { + return + } else if ok { + out = append(out, set[i]) + } + } + + return +} + +// FindByID finds items from slice by its ID property +// +// This function is auto-generated. +func (set ApigwFunctionSet) FindByID(ID uint64) *ApigwFunction { + for i := range set { + if set[i].ID == ID { + return set[i] + } + } + + return nil +} + +// IDs returns a slice of uint64s from all items in the set +// +// This function is auto-generated. +func (set ApigwFunctionSet) IDs() (IDs []uint64) { + IDs = make([]uint64, len(set)) + + for i := range set { + IDs[i] = set[i].ID + } + + return +} + +// Walk iterates through every slice item and calls w(ApigwRoute) err +// +// This function is auto-generated. +func (set ApigwRouteSet) Walk(w func(*ApigwRoute) error) (err error) { + for i := range set { + if err = w(set[i]); err != nil { + return + } + } + + return +} + +// Filter iterates through every slice item, calls f(ApigwRoute) (bool, err) and return filtered slice +// +// This function is auto-generated. +func (set ApigwRouteSet) Filter(f func(*ApigwRoute) (bool, error)) (out ApigwRouteSet, err error) { + var ok bool + out = ApigwRouteSet{} + for i := range set { + if ok, err = f(set[i]); err != nil { + return + } else if ok { + out = append(out, set[i]) + } + } + + return +} + +// FindByID finds items from slice by its ID property +// +// This function is auto-generated. +func (set ApigwRouteSet) FindByID(ID uint64) *ApigwRoute { + for i := range set { + if set[i].ID == ID { + return set[i] + } + } + + return nil +} + +// IDs returns a slice of uint64s from all items in the set +// +// This function is auto-generated. +func (set ApigwRouteSet) IDs() (IDs []uint64) { + IDs = make([]uint64, len(set)) + + for i := range set { + IDs[i] = set[i].ID + } + + return +} + // Walk iterates through every slice item and calls w(Application) err // // This function is auto-generated. @@ -426,62 +538,6 @@ func (set CredentialsSet) IDs() (IDs []uint64) { return } -// Walk iterates through every slice item and calls w(Function) err -// -// This function is auto-generated. -func (set FunctionSet) Walk(w func(*Function) error) (err error) { - for i := range set { - if err = w(set[i]); err != nil { - return - } - } - - return -} - -// Filter iterates through every slice item, calls f(Function) (bool, err) and return filtered slice -// -// This function is auto-generated. -func (set FunctionSet) Filter(f func(*Function) (bool, error)) (out FunctionSet, err error) { - var ok bool - out = FunctionSet{} - for i := range set { - if ok, err = f(set[i]); err != nil { - return - } else if ok { - out = append(out, set[i]) - } - } - - return -} - -// FindByID finds items from slice by its ID property -// -// This function is auto-generated. -func (set FunctionSet) FindByID(ID uint64) *Function { - for i := range set { - if set[i].ID == ID { - return set[i] - } - } - - return nil -} - -// IDs returns a slice of uint64s from all items in the set -// -// This function is auto-generated. -func (set FunctionSet) IDs() (IDs []uint64) { - IDs = make([]uint64, len(set)) - - for i := range set { - IDs[i] = set[i].ID - } - - return -} - // Walk iterates through every slice item and calls w(Reminder) err // // This function is auto-generated. @@ -624,62 +680,6 @@ func (set RoleMemberSet) Filter(f func(*RoleMember) (bool, error)) (out RoleMemb return } -// Walk iterates through every slice item and calls w(Route) err -// -// This function is auto-generated. -func (set RouteSet) Walk(w func(*Route) error) (err error) { - for i := range set { - if err = w(set[i]); err != nil { - return - } - } - - return -} - -// Filter iterates through every slice item, calls f(Route) (bool, err) and return filtered slice -// -// This function is auto-generated. -func (set RouteSet) Filter(f func(*Route) (bool, error)) (out RouteSet, err error) { - var ok bool - out = RouteSet{} - for i := range set { - if ok, err = f(set[i]); err != nil { - return - } else if ok { - out = append(out, set[i]) - } - } - - return -} - -// FindByID finds items from slice by its ID property -// -// This function is auto-generated. -func (set RouteSet) FindByID(ID uint64) *Route { - for i := range set { - if set[i].ID == ID { - return set[i] - } - } - - return nil -} - -// IDs returns a slice of uint64s from all items in the set -// -// This function is auto-generated. -func (set RouteSet) IDs() (IDs []uint64) { - IDs = make([]uint64, len(set)) - - for i := range set { - IDs[i] = set[i].ID - } - - return -} - // Walk iterates through every slice item and calls w(SettingValue) err // // This function is auto-generated. diff --git a/system/types/type_set.gen_test.go b/system/types/type_set.gen_test.go index a74d38013..16d8718c0 100644 --- a/system/types/type_set.gen_test.go +++ b/system/types/type_set.gen_test.go @@ -14,6 +14,186 @@ import ( "testing" ) +func TestApigwFunctionSetWalk(t *testing.T) { + var ( + value = make(ApigwFunctionSet, 3) + req = require.New(t) + ) + + // check walk with no errors + { + err := value.Walk(func(*ApigwFunction) error { + return nil + }) + req.NoError(err) + } + + // check walk with error + req.Error(value.Walk(func(*ApigwFunction) error { return fmt.Errorf("walk error") })) +} + +func TestApigwFunctionSetFilter(t *testing.T) { + var ( + value = make(ApigwFunctionSet, 3) + req = require.New(t) + ) + + // filter nothing + { + set, err := value.Filter(func(*ApigwFunction) (bool, error) { + return true, nil + }) + req.NoError(err) + req.Equal(len(set), len(value)) + } + + // filter one item + { + found := false + set, err := value.Filter(func(*ApigwFunction) (bool, error) { + if !found { + found = true + return found, nil + } + return false, nil + }) + req.NoError(err) + req.Len(set, 1) + } + + // filter error + { + _, err := value.Filter(func(*ApigwFunction) (bool, error) { + return false, fmt.Errorf("filter error") + }) + req.Error(err) + } +} + +func TestApigwFunctionSetIDs(t *testing.T) { + var ( + value = make(ApigwFunctionSet, 3) + req = require.New(t) + ) + + // construct objects + value[0] = new(ApigwFunction) + value[1] = new(ApigwFunction) + value[2] = new(ApigwFunction) + // set ids + value[0].ID = 1 + value[1].ID = 2 + value[2].ID = 3 + + // Find existing + { + val := value.FindByID(2) + req.Equal(uint64(2), val.ID) + } + + // Find non-existing + { + val := value.FindByID(4) + req.Nil(val) + } + + // List IDs from set + { + val := value.IDs() + req.Equal(len(val), len(value)) + } +} + +func TestApigwRouteSetWalk(t *testing.T) { + var ( + value = make(ApigwRouteSet, 3) + req = require.New(t) + ) + + // check walk with no errors + { + err := value.Walk(func(*ApigwRoute) error { + return nil + }) + req.NoError(err) + } + + // check walk with error + req.Error(value.Walk(func(*ApigwRoute) error { return fmt.Errorf("walk error") })) +} + +func TestApigwRouteSetFilter(t *testing.T) { + var ( + value = make(ApigwRouteSet, 3) + req = require.New(t) + ) + + // filter nothing + { + set, err := value.Filter(func(*ApigwRoute) (bool, error) { + return true, nil + }) + req.NoError(err) + req.Equal(len(set), len(value)) + } + + // filter one item + { + found := false + set, err := value.Filter(func(*ApigwRoute) (bool, error) { + if !found { + found = true + return found, nil + } + return false, nil + }) + req.NoError(err) + req.Len(set, 1) + } + + // filter error + { + _, err := value.Filter(func(*ApigwRoute) (bool, error) { + return false, fmt.Errorf("filter error") + }) + req.Error(err) + } +} + +func TestApigwRouteSetIDs(t *testing.T) { + var ( + value = make(ApigwRouteSet, 3) + req = require.New(t) + ) + + // construct objects + value[0] = new(ApigwRoute) + value[1] = new(ApigwRoute) + value[2] = new(ApigwRoute) + // set ids + value[0].ID = 1 + value[1].ID = 2 + value[2].ID = 3 + + // Find existing + { + val := value.FindByID(2) + req.Equal(uint64(2), val.ID) + } + + // Find non-existing + { + val := value.FindByID(4) + req.Nil(val) + } + + // List IDs from set + { + val := value.IDs() + req.Equal(len(val), len(value)) + } +} + func TestApplicationSetWalk(t *testing.T) { var ( value = make(ApplicationSet, 3) @@ -576,96 +756,6 @@ func TestCredentialsSetIDs(t *testing.T) { } } -func TestFunctionSetWalk(t *testing.T) { - var ( - value = make(FunctionSet, 3) - req = require.New(t) - ) - - // check walk with no errors - { - err := value.Walk(func(*Function) error { - return nil - }) - req.NoError(err) - } - - // check walk with error - req.Error(value.Walk(func(*Function) error { return fmt.Errorf("walk error") })) -} - -func TestFunctionSetFilter(t *testing.T) { - var ( - value = make(FunctionSet, 3) - req = require.New(t) - ) - - // filter nothing - { - set, err := value.Filter(func(*Function) (bool, error) { - return true, nil - }) - req.NoError(err) - req.Equal(len(set), len(value)) - } - - // filter one item - { - found := false - set, err := value.Filter(func(*Function) (bool, error) { - if !found { - found = true - return found, nil - } - return false, nil - }) - req.NoError(err) - req.Len(set, 1) - } - - // filter error - { - _, err := value.Filter(func(*Function) (bool, error) { - return false, fmt.Errorf("filter error") - }) - req.Error(err) - } -} - -func TestFunctionSetIDs(t *testing.T) { - var ( - value = make(FunctionSet, 3) - req = require.New(t) - ) - - // construct objects - value[0] = new(Function) - value[1] = new(Function) - value[2] = new(Function) - // set ids - value[0].ID = 1 - value[1].ID = 2 - value[2].ID = 3 - - // Find existing - { - val := value.FindByID(2) - req.Equal(uint64(2), val.ID) - } - - // Find non-existing - { - val := value.FindByID(4) - req.Nil(val) - } - - // List IDs from set - { - val := value.IDs() - req.Equal(len(val), len(value)) - } -} - func TestReminderSetWalk(t *testing.T) { var ( value = make(ReminderSet, 3) @@ -902,96 +992,6 @@ func TestRoleMemberSetFilter(t *testing.T) { } } -func TestRouteSetWalk(t *testing.T) { - var ( - value = make(RouteSet, 3) - req = require.New(t) - ) - - // check walk with no errors - { - err := value.Walk(func(*Route) error { - return nil - }) - req.NoError(err) - } - - // check walk with error - req.Error(value.Walk(func(*Route) error { return fmt.Errorf("walk error") })) -} - -func TestRouteSetFilter(t *testing.T) { - var ( - value = make(RouteSet, 3) - req = require.New(t) - ) - - // filter nothing - { - set, err := value.Filter(func(*Route) (bool, error) { - return true, nil - }) - req.NoError(err) - req.Equal(len(set), len(value)) - } - - // filter one item - { - found := false - set, err := value.Filter(func(*Route) (bool, error) { - if !found { - found = true - return found, nil - } - return false, nil - }) - req.NoError(err) - req.Len(set, 1) - } - - // filter error - { - _, err := value.Filter(func(*Route) (bool, error) { - return false, fmt.Errorf("filter error") - }) - req.Error(err) - } -} - -func TestRouteSetIDs(t *testing.T) { - var ( - value = make(RouteSet, 3) - req = require.New(t) - ) - - // construct objects - value[0] = new(Route) - value[1] = new(Route) - value[2] = new(Route) - // set ids - value[0].ID = 1 - value[1].ID = 2 - value[2].ID = 3 - - // Find existing - { - val := value.FindByID(2) - req.Equal(uint64(2), val.ID) - } - - // Find non-existing - { - val := value.FindByID(4) - req.Nil(val) - } - - // List IDs from set - { - val := value.IDs() - req.Equal(len(val), len(value)) - } -} - func TestSettingValueSetWalk(t *testing.T) { var ( value = make(SettingValueSet, 3) diff --git a/system/types/types.yaml b/system/types/types.yaml index b712fb094..f8a0648be 100644 --- a/system/types/types.yaml +++ b/system/types/types.yaml @@ -21,5 +21,5 @@ types: noIdField: true Template: labelResourceType: template - Route: {} - Function: {} \ No newline at end of file + ApigwRoute: {} + ApigwFunction: {} \ No newline at end of file