snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
This commit is contained in:
2
server/pkg/corredor/.gitignore
vendored
Normal file
2
server/pkg/corredor/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.zip
|
||||
*.proto
|
||||
43
server/pkg/corredor/Makefile
Normal file
43
server/pkg/corredor/Makefile
Normal file
@@ -0,0 +1,43 @@
|
||||
.PHONY: generate
|
||||
|
||||
GOPATH ?= $(go env GOPATH)
|
||||
BRANCH ?= develop
|
||||
ZIP ?= $(BRANCH).zip
|
||||
DIR ?= corteza-protobuf-$(BRANCH)
|
||||
PROTO ?= service-corredor.proto
|
||||
URL ?= https://github.com/cortezaproject/corteza-protobuf/archive/$(ZIP)
|
||||
|
||||
PARTS ?= compose system
|
||||
OUTPUTS ?= $(addsuffix /static.go, $(PARTS))
|
||||
PROTOC ?= /usr/local/bin/protoc
|
||||
CURL ?= /usr/bin/curl
|
||||
UNZIP ?= /usr/bin/unzip
|
||||
|
||||
PROTOGEN ?= $(GOPATH)/bin/protoc-gen-go
|
||||
PROTOGEN_GRPC ?= $(GOPATH)/bin/protoc-gen-go-grpc
|
||||
|
||||
|
||||
generate:
|
||||
PATH=$(PATH):$(GOPATH)/bin protoc --go_out=. --go-grpc_out=. --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative $(PROTO)
|
||||
|
||||
$(PROTO): $(ZIP)
|
||||
$(UNZIP) -qq -o $(ZIP)
|
||||
mv $(DIR)/$(PROTO) $(PROTO)
|
||||
rm -rf $(DIR)
|
||||
|
||||
$(ZIP):
|
||||
curl -o $(ZIP) -s --location "$(URL)"
|
||||
|
||||
clear:
|
||||
rm -f $(ZIP)
|
||||
|
||||
deps: $(PROTOC) $(PROTOGEN) $(PROTOGEN_GRPC)
|
||||
|
||||
$(PROTOC):
|
||||
$(MAKE) --directory=../../ $(PROTOC)
|
||||
|
||||
$(PROTOGEN):
|
||||
$(MAKE) --directory=../../ $(PROTOGEN)
|
||||
|
||||
$(PROTOGEN_GRPC):
|
||||
$(MAKE) --directory=../../ $(PROTOGEN_GRPC)
|
||||
8
server/pkg/corredor/README.adoc
Normal file
8
server/pkg/corredor/README.adoc
Normal file
@@ -0,0 +1,8 @@
|
||||
# pkg/corredor
|
||||
|
||||
This package is an interface to Corredor script runner service.
|
||||
|
||||
.It provides:
|
||||
- Connection to Corredor service
|
||||
- Subscriber service to handle events raised from Corteza services
|
||||
- Scheduling service for deferred events (scheduled, interval)
|
||||
27
server/pkg/corredor/cache.go
Normal file
27
server/pkg/corredor/cache.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package corredor
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza/server/system/types"
|
||||
)
|
||||
|
||||
type (
|
||||
userLookupCache struct {
|
||||
err error
|
||||
user *types.User
|
||||
}
|
||||
|
||||
userLookupCacheMap map[string]userLookupCache
|
||||
)
|
||||
|
||||
func (m userLookupCacheMap) lookup(key string, lookup func() (*types.User, error)) (*types.User, error) {
|
||||
if c, ok := m[key]; ok {
|
||||
return c.user, c.err
|
||||
}
|
||||
|
||||
c := userLookupCache{}
|
||||
c.user, c.err = lookup()
|
||||
|
||||
m[key] = c
|
||||
|
||||
return c.user, c.err
|
||||
}
|
||||
99
server/pkg/corredor/conn.go
Normal file
99
server/pkg/corredor/conn.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package corredor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/options"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
// Corredor standard connector to Corredor service via gRPC
|
||||
func NewConnection(ctx context.Context, opt options.CorredorOpt, logger *zap.Logger) (c *grpc.ClientConn, err error) {
|
||||
log := logger.Named("conn")
|
||||
|
||||
if !opt.Enabled {
|
||||
// Do not connect when script runner is not enabled
|
||||
log.Info("corredor disabled (CORREDOR_ENABLED=false)")
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
dialOpts = make([]grpc.DialOption, 0)
|
||||
|
||||
paths = map[string]string{
|
||||
"corredor CA public key": opt.TlsCertCA,
|
||||
"corredor client public key": opt.TlsCertPublic,
|
||||
"corredor client private key": opt.TlsCertPrivate,
|
||||
}
|
||||
|
||||
expl = "\n\n" +
|
||||
"Check and change configured path to Corredor TLS certificates " +
|
||||
"in env var CORREDOR_CLIENT_CERTIFICATES_PATH or disable " +
|
||||
"this check with CORREDOR_CLIENT_CERTIFICATES_ENABLED=false" +
|
||||
"\n\n"
|
||||
)
|
||||
|
||||
// opt.PublicKey = ""
|
||||
if opt.TlsCertEnabled {
|
||||
// Check paths
|
||||
log.Debug("connecting with TLS certificates enabled (CORREDOR_CLIENT_CERTIFICATES_ENABLED=true)")
|
||||
|
||||
for label, path := range paths {
|
||||
if _, err = os.Stat(path); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("%s (%s) not found"+expl, label, path)
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("%s (%s) could not be loaded: %s"+expl, label, path, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Load the certificates from disk
|
||||
certificate, err := tls.LoadX509KeyPair(opt.TlsCertPublic, opt.TlsCertPrivate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not load client key pair: %s"+expl, err)
|
||||
}
|
||||
|
||||
// Create a certificate pool from the certificate authority
|
||||
certPool := x509.NewCertPool()
|
||||
ca, err := ioutil.ReadFile(opt.TlsCertCA)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read ca certificate: %s"+expl, err)
|
||||
}
|
||||
|
||||
// Append the client certificates from the CA
|
||||
if ok := certPool.AppendCertsFromPEM(ca); !ok {
|
||||
return nil, errors.New("failed to append ca certs" + expl)
|
||||
}
|
||||
|
||||
crds := credentials.NewTLS(&tls.Config{
|
||||
ServerName: opt.TlsServerName,
|
||||
Certificates: []tls.Certificate{certificate},
|
||||
RootCAs: certPool,
|
||||
})
|
||||
|
||||
dialOpts = append(dialOpts, grpc.WithTransportCredentials(crds))
|
||||
} else {
|
||||
log.Debug("connecting without TLS certificates " +
|
||||
"(this is OK if you are using private or internal docker network)")
|
||||
dialOpts = append(dialOpts, grpc.WithInsecure())
|
||||
}
|
||||
|
||||
if opt.MaxReceiveMessageSize > 0 {
|
||||
dialOpts = append(dialOpts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(opt.MaxReceiveMessageSize)))
|
||||
}
|
||||
|
||||
if opt.MaxBackoffDelay > 0 {
|
||||
dialOpts = append(dialOpts, grpc.WithBackoffMaxDelay(opt.MaxBackoffDelay))
|
||||
}
|
||||
|
||||
log.Info("connecting to corredor", zap.String("addr", opt.Addr))
|
||||
|
||||
return grpc.DialContext(ctx, opt.Addr, dialOpts...)
|
||||
}
|
||||
93
server/pkg/corredor/conn_test.go
Normal file
93
server/pkg/corredor/conn_test.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package corredor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"go.uber.org/zap"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/connectivity"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/options"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
}
|
||||
|
||||
func TestNewConnectionWithDisabled(t *testing.T) {
|
||||
c, err := NewConnection(nil, options.CorredorOpt{Enabled: false}, zap.NewNop())
|
||||
assert.Nil(t, c)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestNewConnection(t *testing.T) {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
|
||||
a = assert.New(t)
|
||||
wg = &sync.WaitGroup{}
|
||||
|
||||
lstnr = openListener(t)
|
||||
grpcServer = grpc.NewServer()
|
||||
|
||||
opt = options.CorredorOpt{
|
||||
Enabled: true,
|
||||
MaxBackoffDelay: 1,
|
||||
Addr: lstnr.Addr().String(),
|
||||
}
|
||||
)
|
||||
|
||||
a.NotNil(lstnr)
|
||||
defer lstnr.Close()
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
if err := grpcServer.Serve(lstnr); err != nil && err != grpc.ErrServerStopped {
|
||||
a.NoError(err)
|
||||
}
|
||||
}()
|
||||
|
||||
grpcClientConn, err := NewConnection(ctx, opt, zap.NewNop())
|
||||
a.NoError(err)
|
||||
|
||||
// NewService(dbgLog, opt)
|
||||
|
||||
grpcClientConn.WaitForStateChange(ctx, connectivity.Ready)
|
||||
grpcServer.GracefulStop()
|
||||
lstnr.Close()
|
||||
|
||||
t.Log("waiting for connection to close")
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func openListener(t *testing.T) (ln net.Listener) {
|
||||
var (
|
||||
tries = 1000
|
||||
minPort = 50000
|
||||
maxPort = 63000
|
||||
port int
|
||||
err error
|
||||
)
|
||||
|
||||
for tries > 0 {
|
||||
port = minPort + rand.Intn(maxPort-minPort)
|
||||
t.Logf("trying to find available port for gRPC connection: %d", port)
|
||||
ln, err = net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
|
||||
if err == nil {
|
||||
return ln
|
||||
}
|
||||
|
||||
t.Errorf("error: %s", err)
|
||||
tries--
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
122
server/pkg/corredor/filter.go
Normal file
122
server/pkg/corredor/filter.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package corredor
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza/server/pkg/slice"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type (
|
||||
Filter struct {
|
||||
ResourceTypePrefixes []string `json:"resourceTypePrefixes"`
|
||||
ResourceTypes []string `json:"resourceTypes"`
|
||||
EventTypes []string `json:"eventTypes"`
|
||||
ExcludeServerScripts bool `json:"excludeServerScripts"`
|
||||
ExcludeClientScripts bool `json:"excludeClientScripts"`
|
||||
ExcludeInvalid bool `json:"excludeInvalid"`
|
||||
|
||||
//Page uint `json:"page"`
|
||||
//PerPage uint `json:"perPage"`
|
||||
Count uint `json:"count"`
|
||||
}
|
||||
)
|
||||
|
||||
func (f Filter) makeFilterFn() func(s *Script) (b bool, err error) {
|
||||
collectTypes := func(tt ...*Trigger) (rr []string, ee []string) {
|
||||
for _, t := range tt {
|
||||
rr = append(rr, t.ResourceTypes...)
|
||||
ee = append(ee, t.EventTypes...)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return func(s *Script) (b bool, err error) {
|
||||
if f.ExcludeInvalid {
|
||||
if len(s.Errors) > 0 {
|
||||
// Skip scripts with errors
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
resourceTypes, eventTypes = collectTypes(s.Triggers...)
|
||||
)
|
||||
|
||||
if !f.checkPrefixes(resourceTypes...) {
|
||||
return
|
||||
}
|
||||
|
||||
if !f.checkEventTypes(eventTypes...) {
|
||||
return
|
||||
}
|
||||
|
||||
if !f.checkResourceTypes(resourceTypes...) {
|
||||
return
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
// parses resource type prefixes, adds ui: and service prefix if not present
|
||||
func (f *Filter) procRTPrefixes(service string) {
|
||||
var (
|
||||
hasService, hasUi bool
|
||||
)
|
||||
|
||||
if service == "" {
|
||||
return
|
||||
}
|
||||
|
||||
for i, rtp := range f.ResourceTypePrefixes {
|
||||
if rtp == service {
|
||||
hasService = true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(rtp, "ui") {
|
||||
hasUi = true
|
||||
}
|
||||
|
||||
// Check all requested resource-type prefixed and make sure
|
||||
// all start with either "ui" or resourcePrefix + ":"
|
||||
if !strings.HasPrefix(rtp, "ui:") && !strings.HasPrefix(rtp, service) {
|
||||
f.ResourceTypePrefixes[i] = service + ":" + rtp
|
||||
}
|
||||
}
|
||||
|
||||
if !hasService {
|
||||
f.ResourceTypePrefixes = append(f.ResourceTypePrefixes, service)
|
||||
}
|
||||
|
||||
if !hasUi {
|
||||
f.ResourceTypePrefixes = append(f.ResourceTypePrefixes, "ui:")
|
||||
}
|
||||
}
|
||||
|
||||
func (f Filter) checkPrefixes(rr ...string) bool {
|
||||
if len(f.ResourceTypePrefixes) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(rr) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, p := range f.ResourceTypePrefixes {
|
||||
for _, r := range rr {
|
||||
if strings.HasPrefix(r, p) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (f Filter) checkResourceTypes(rr ...string) bool {
|
||||
return len(f.ResourceTypes) == 0 || len(slice.IntersectStrings(f.ResourceTypes, rr)) > 0
|
||||
}
|
||||
|
||||
func (f Filter) checkEventTypes(ee ...string) bool {
|
||||
return len(f.EventTypes) == 0 || len(slice.IntersectStrings(f.EventTypes, ee)) > 0
|
||||
}
|
||||
138
server/pkg/corredor/filter_test.go
Normal file
138
server/pkg/corredor/filter_test.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package corredor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFilterResourcePrefixing(t *testing.T) {
|
||||
f := &Filter{
|
||||
ResourceTypePrefixes: []string{
|
||||
"system",
|
||||
"system:one",
|
||||
"two",
|
||||
"ui:admin:foo",
|
||||
"ui:admin",
|
||||
},
|
||||
}
|
||||
|
||||
f.procRTPrefixes("system")
|
||||
|
||||
assert.New(t).Equal(
|
||||
f.ResourceTypePrefixes,
|
||||
[]string{
|
||||
"system",
|
||||
"system:one",
|
||||
"system:two",
|
||||
"ui:admin:foo",
|
||||
"ui:admin",
|
||||
},
|
||||
)
|
||||
|
||||
f = &Filter{}
|
||||
|
||||
f.procRTPrefixes("system")
|
||||
|
||||
assert.New(t).Equal(
|
||||
f.ResourceTypePrefixes,
|
||||
[]string{
|
||||
"system",
|
||||
"ui:",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func TestResourceTypesChecker(t *testing.T) {
|
||||
var (
|
||||
a = assert.New(t)
|
||||
)
|
||||
|
||||
// nothing requested in the filter
|
||||
a.True(Filter{ResourceTypes: []string{}}.checkResourceTypes("c"))
|
||||
|
||||
// no resources
|
||||
a.False(Filter{ResourceTypes: []string{"a", "b"}}.checkResourceTypes())
|
||||
|
||||
// no resources, no filter
|
||||
a.True(Filter{}.checkResourceTypes())
|
||||
|
||||
a.True(Filter{ResourceTypes: []string{"a"}}.checkResourceTypes("a", "b"))
|
||||
|
||||
a.False(Filter{ResourceTypes: []string{"c"}}.checkResourceTypes("a", "b"))
|
||||
}
|
||||
|
||||
func TestEventTypesChecker(t *testing.T) {
|
||||
var (
|
||||
a = assert.New(t)
|
||||
)
|
||||
|
||||
// nothing requested in the filter
|
||||
a.True(Filter{EventTypes: []string{}}.checkEventTypes("c"))
|
||||
|
||||
// no resources
|
||||
a.False(Filter{EventTypes: []string{"a", "b"}}.checkEventTypes())
|
||||
|
||||
// no resources, no filter
|
||||
a.True(Filter{}.checkEventTypes())
|
||||
|
||||
a.True(Filter{EventTypes: []string{"a"}}.checkEventTypes("a", "b"))
|
||||
|
||||
a.False(Filter{EventTypes: []string{"c"}}.checkEventTypes("a", "b"))
|
||||
}
|
||||
|
||||
func TestScriptFilterMaker(t *testing.T) {
|
||||
var (
|
||||
s1 = &Script{
|
||||
Triggers: []*Trigger{
|
||||
&Trigger{
|
||||
EventTypes: []string{"ev"},
|
||||
ResourceTypes: []string{"res"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
s2 = &Script{
|
||||
Triggers: []*Trigger{
|
||||
&Trigger{
|
||||
EventTypes: []string{"foo"},
|
||||
ResourceTypes: []string{"bar"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
s3 = &Script{
|
||||
Triggers: []*Trigger{
|
||||
&Trigger{
|
||||
EventTypes: []string{"not-a-match"},
|
||||
ResourceTypes: []string{"not-a-match"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
a = assert.New(t)
|
||||
strip = func(b bool, _ error) bool { return b }
|
||||
|
||||
f func(s *Script) (b bool, err error)
|
||||
)
|
||||
|
||||
f = Filter{}.makeFilterFn()
|
||||
a.True(strip(f(s1)))
|
||||
a.True(strip(f(s2)))
|
||||
a.True(strip(f(s3)))
|
||||
|
||||
f = Filter{ResourceTypes: []string{"res"}}.makeFilterFn()
|
||||
a.True(strip(f(s1)))
|
||||
a.False(strip(f(s2)))
|
||||
a.False(strip(f(s3)))
|
||||
|
||||
f = Filter{EventTypes: []string{"ev"}}.makeFilterFn()
|
||||
a.True(strip(f(s1)))
|
||||
a.False(strip(f(s2)))
|
||||
a.False(strip(f(s3)))
|
||||
|
||||
f = Filter{EventTypes: []string{"ev", "foo"}}.makeFilterFn()
|
||||
a.True(strip(f(s1)))
|
||||
a.True(strip(f(s2)))
|
||||
a.False(strip(f(s3)))
|
||||
}
|
||||
34
server/pkg/corredor/healthcheck.go
Normal file
34
server/pkg/corredor/healthcheck.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package corredor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/grpc/connectivity"
|
||||
)
|
||||
|
||||
// Healtcheck for global
|
||||
func Healthcheck(_ context.Context) error {
|
||||
svc := Service()
|
||||
|
||||
if svc == nil {
|
||||
return fmt.Errorf("uninitialized")
|
||||
}
|
||||
|
||||
if !svc.opt.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
if svc.conn == nil {
|
||||
// working around edge-case where health-check is called
|
||||
// but conn variable is not set;
|
||||
// prevents nil pointer dereference error
|
||||
return nil
|
||||
}
|
||||
|
||||
if state := svc.conn.GetState(); state != connectivity.Ready {
|
||||
return fmt.Errorf("connection is %s", state)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
16
server/pkg/corredor/healthcheck_test.go
Normal file
16
server/pkg/corredor/healthcheck_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package corredor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/options"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// tested with
|
||||
// go test -count 10 -race -run TestDataRace ./pkg/corredor/...
|
||||
func TestDataRace(t *testing.T) {
|
||||
go Setup(zap.NewNop(), options.CorredorOpt{})
|
||||
go Healthcheck(context.Background())
|
||||
}
|
||||
1517
server/pkg/corredor/service-corredor.pb.go
Normal file
1517
server/pkg/corredor/service-corredor.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
306
server/pkg/corredor/service-corredor_grpc.pb.go
Normal file
306
server/pkg/corredor/service-corredor_grpc.pb.go
Normal file
@@ -0,0 +1,306 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
|
||||
package corredor
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// ServerScriptsClient is the client API for ServerScripts service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type ServerScriptsClient interface {
|
||||
// Executes server script
|
||||
Exec(ctx context.Context, in *ExecRequest, opts ...grpc.CallOption) (*ExecResponse, error)
|
||||
// List of server scripts
|
||||
List(ctx context.Context, in *ServerScriptListRequest, opts ...grpc.CallOption) (*ServerScriptListResponse, error)
|
||||
}
|
||||
|
||||
type serverScriptsClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewServerScriptsClient(cc grpc.ClientConnInterface) ServerScriptsClient {
|
||||
return &serverScriptsClient{cc}
|
||||
}
|
||||
|
||||
func (c *serverScriptsClient) Exec(ctx context.Context, in *ExecRequest, opts ...grpc.CallOption) (*ExecResponse, error) {
|
||||
out := new(ExecResponse)
|
||||
err := c.cc.Invoke(ctx, "/corredor.ServerScripts/Exec", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *serverScriptsClient) List(ctx context.Context, in *ServerScriptListRequest, opts ...grpc.CallOption) (*ServerScriptListResponse, error) {
|
||||
out := new(ServerScriptListResponse)
|
||||
err := c.cc.Invoke(ctx, "/corredor.ServerScripts/List", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ServerScriptsServer is the server API for ServerScripts service.
|
||||
// All implementations must embed UnimplementedServerScriptsServer
|
||||
// for forward compatibility
|
||||
type ServerScriptsServer interface {
|
||||
// Executes server script
|
||||
Exec(context.Context, *ExecRequest) (*ExecResponse, error)
|
||||
// List of server scripts
|
||||
List(context.Context, *ServerScriptListRequest) (*ServerScriptListResponse, error)
|
||||
mustEmbedUnimplementedServerScriptsServer()
|
||||
}
|
||||
|
||||
// UnimplementedServerScriptsServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedServerScriptsServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedServerScriptsServer) Exec(context.Context, *ExecRequest) (*ExecResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Exec not implemented")
|
||||
}
|
||||
func (UnimplementedServerScriptsServer) List(context.Context, *ServerScriptListRequest) (*ServerScriptListResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method List not implemented")
|
||||
}
|
||||
func (UnimplementedServerScriptsServer) mustEmbedUnimplementedServerScriptsServer() {}
|
||||
|
||||
// UnsafeServerScriptsServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to ServerScriptsServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeServerScriptsServer interface {
|
||||
mustEmbedUnimplementedServerScriptsServer()
|
||||
}
|
||||
|
||||
func RegisterServerScriptsServer(s *grpc.Server, srv ServerScriptsServer) {
|
||||
s.RegisterService(&_ServerScripts_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _ServerScripts_Exec_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ExecRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ServerScriptsServer).Exec(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/corredor.ServerScripts/Exec",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ServerScriptsServer).Exec(ctx, req.(*ExecRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _ServerScripts_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ServerScriptListRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ServerScriptsServer).List(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/corredor.ServerScripts/List",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ServerScriptsServer).List(ctx, req.(*ServerScriptListRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _ServerScripts_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "corredor.ServerScripts",
|
||||
HandlerType: (*ServerScriptsServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Exec",
|
||||
Handler: _ServerScripts_Exec_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "List",
|
||||
Handler: _ServerScripts_List_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "service-corredor.proto",
|
||||
}
|
||||
|
||||
// ClientScriptsClient is the client API for ClientScripts service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type ClientScriptsClient interface {
|
||||
// Bundles
|
||||
Bundle(ctx context.Context, in *BundleRequest, opts ...grpc.CallOption) (*BundleResponse, error)
|
||||
// List of client scripts
|
||||
List(ctx context.Context, in *ClientScriptListRequest, opts ...grpc.CallOption) (*ClientScriptListResponse, error)
|
||||
}
|
||||
|
||||
type clientScriptsClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewClientScriptsClient(cc grpc.ClientConnInterface) ClientScriptsClient {
|
||||
return &clientScriptsClient{cc}
|
||||
}
|
||||
|
||||
func (c *clientScriptsClient) Bundle(ctx context.Context, in *BundleRequest, opts ...grpc.CallOption) (*BundleResponse, error) {
|
||||
out := new(BundleResponse)
|
||||
err := c.cc.Invoke(ctx, "/corredor.ClientScripts/Bundle", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *clientScriptsClient) List(ctx context.Context, in *ClientScriptListRequest, opts ...grpc.CallOption) (*ClientScriptListResponse, error) {
|
||||
out := new(ClientScriptListResponse)
|
||||
err := c.cc.Invoke(ctx, "/corredor.ClientScripts/List", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ClientScriptsServer is the server API for ClientScripts service.
|
||||
// All implementations must embed UnimplementedClientScriptsServer
|
||||
// for forward compatibility
|
||||
type ClientScriptsServer interface {
|
||||
// Bundles
|
||||
Bundle(context.Context, *BundleRequest) (*BundleResponse, error)
|
||||
// List of client scripts
|
||||
List(context.Context, *ClientScriptListRequest) (*ClientScriptListResponse, error)
|
||||
mustEmbedUnimplementedClientScriptsServer()
|
||||
}
|
||||
|
||||
// UnimplementedClientScriptsServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedClientScriptsServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedClientScriptsServer) Bundle(context.Context, *BundleRequest) (*BundleResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Bundle not implemented")
|
||||
}
|
||||
func (UnimplementedClientScriptsServer) List(context.Context, *ClientScriptListRequest) (*ClientScriptListResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method List not implemented")
|
||||
}
|
||||
func (UnimplementedClientScriptsServer) mustEmbedUnimplementedClientScriptsServer() {}
|
||||
|
||||
// UnsafeClientScriptsServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to ClientScriptsServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeClientScriptsServer interface {
|
||||
mustEmbedUnimplementedClientScriptsServer()
|
||||
}
|
||||
|
||||
func RegisterClientScriptsServer(s *grpc.Server, srv ClientScriptsServer) {
|
||||
s.RegisterService(&_ClientScripts_serviceDesc, srv)
|
||||
}
|
||||
|
||||
func _ClientScripts_Bundle_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(BundleRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ClientScriptsServer).Bundle(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/corredor.ClientScripts/Bundle",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ClientScriptsServer).Bundle(ctx, req.(*BundleRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _ClientScripts_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ClientScriptListRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(ClientScriptsServer).List(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/corredor.ClientScripts/List",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(ClientScriptsServer).List(ctx, req.(*ClientScriptListRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _ClientScripts_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "corredor.ClientScripts",
|
||||
HandlerType: (*ClientScriptsServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "Bundle",
|
||||
Handler: _ClientScripts_Bundle_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "List",
|
||||
Handler: _ClientScripts_List_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "service-corredor.proto",
|
||||
}
|
||||
|
||||
// StorageClient is the client API for Storage service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type StorageClient interface {
|
||||
}
|
||||
|
||||
type storageClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewStorageClient(cc grpc.ClientConnInterface) StorageClient {
|
||||
return &storageClient{cc}
|
||||
}
|
||||
|
||||
// StorageServer is the server API for Storage service.
|
||||
// All implementations must embed UnimplementedStorageServer
|
||||
// for forward compatibility
|
||||
type StorageServer interface {
|
||||
mustEmbedUnimplementedStorageServer()
|
||||
}
|
||||
|
||||
// UnimplementedStorageServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedStorageServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedStorageServer) mustEmbedUnimplementedStorageServer() {}
|
||||
|
||||
// UnsafeStorageServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to StorageServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeStorageServer interface {
|
||||
mustEmbedUnimplementedStorageServer()
|
||||
}
|
||||
|
||||
func RegisterStorageServer(s *grpc.Server, srv StorageServer) {
|
||||
s.RegisterService(&_Storage_serviceDesc, srv)
|
||||
}
|
||||
|
||||
var _Storage_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "corredor.Storage",
|
||||
HandlerType: (*StorageServer)(nil),
|
||||
Methods: []grpc.MethodDesc{},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "service-corredor.proto",
|
||||
}
|
||||
1034
server/pkg/corredor/service.go
Normal file
1034
server/pkg/corredor/service.go
Normal file
File diff suppressed because it is too large
Load Diff
243
server/pkg/corredor/service_test.go
Normal file
243
server/pkg/corredor/service_test.go
Normal file
@@ -0,0 +1,243 @@
|
||||
package corredor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza/server/pkg/eventbus"
|
||||
"github.com/cortezaproject/corteza/server/pkg/logger"
|
||||
"github.com/cortezaproject/corteza/server/pkg/options"
|
||||
"github.com/cortezaproject/corteza/server/system/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
mockEvent struct {
|
||||
rType string
|
||||
eType string
|
||||
match func(matcher eventbus.ConstraintMatcher) bool
|
||||
}
|
||||
|
||||
mockUserSvc struct {
|
||||
user *types.User
|
||||
err error
|
||||
}
|
||||
mockRoleSvc struct {
|
||||
role *types.Role
|
||||
err error
|
||||
}
|
||||
)
|
||||
|
||||
func (e mockEvent) ResourceType() string {
|
||||
return e.rType
|
||||
}
|
||||
|
||||
func (e mockEvent) EventType() string {
|
||||
return e.eType
|
||||
}
|
||||
|
||||
func (e mockEvent) Encode() (map[string][]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (e mockEvent) Decode(map[string][]byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e mockEvent) Match(matcher eventbus.ConstraintMatcher) bool {
|
||||
if e.match == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return e.match(matcher)
|
||||
}
|
||||
|
||||
func (u *mockUserSvc) FindByAny(context.Context, interface{}) (*types.User, error) {
|
||||
return u.user, u.err
|
||||
}
|
||||
|
||||
func (u *mockRoleSvc) FindByAny(context.Context, interface{}) (*types.Role, error) {
|
||||
return u.role, u.err
|
||||
}
|
||||
|
||||
func TestFindOnManual(t *testing.T) {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
|
||||
svc = &service{
|
||||
denyExec: make(map[string]map[uint64]bool),
|
||||
sScripts: ScriptSet{
|
||||
&Script{
|
||||
Name: "s1",
|
||||
Triggers: []*Trigger{
|
||||
&Trigger{
|
||||
EventTypes: []string{"ev"},
|
||||
ResourceTypes: []string{"res"},
|
||||
},
|
||||
},
|
||||
},
|
||||
&Script{
|
||||
Name: "s2",
|
||||
Triggers: []*Trigger{
|
||||
&Trigger{
|
||||
EventTypes: []string{"foo"},
|
||||
ResourceTypes: []string{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
//&Script{
|
||||
// Triggers: []*Trigger{
|
||||
// &Trigger{
|
||||
// EventTypes: []string{"not-a-match"},
|
||||
// ResourceTypes: []string{"not-a-match"},
|
||||
// },
|
||||
// },
|
||||
//},
|
||||
},
|
||||
cScripts: ScriptSet{
|
||||
&Script{
|
||||
Name: "s3",
|
||||
Triggers: []*Trigger{
|
||||
&Trigger{
|
||||
EventTypes: []string{"ev"},
|
||||
ResourceTypes: []string{"res"},
|
||||
},
|
||||
},
|
||||
},
|
||||
&Script{
|
||||
Name: "s4",
|
||||
Triggers: []*Trigger{
|
||||
&Trigger{
|
||||
EventTypes: []string{"foo"},
|
||||
ResourceTypes: []string{"bar"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
filter = Filter{
|
||||
ResourceTypes: []string{"res"},
|
||||
EventTypes: []string{"ev"},
|
||||
ExcludeServerScripts: false,
|
||||
ExcludeClientScripts: false,
|
||||
}
|
||||
|
||||
o, _, err = svc.Find(ctx, filter)
|
||||
|
||||
a = assert.New(t)
|
||||
)
|
||||
|
||||
a.NoError(err)
|
||||
a.Len(o, 2)
|
||||
}
|
||||
|
||||
func TestGlobals(t *testing.T) {
|
||||
var (
|
||||
a = assert.New(t)
|
||||
)
|
||||
|
||||
gCorredor = NewService(zap.NewNop(), options.CorredorOpt{})
|
||||
a.Equal(gCorredor, Service())
|
||||
a.NoError(Setup(zap.NewNop(), options.CorredorOpt{}))
|
||||
a.NotNil(gCorredor)
|
||||
|
||||
gCorredor = nil
|
||||
a.NoError(Setup(zap.NewNop(), options.CorredorOpt{}))
|
||||
a.Equal(gCorredor, Service())
|
||||
a.NotNil(gCorredor)
|
||||
}
|
||||
|
||||
func TestServiceBasics(t *testing.T) {
|
||||
var (
|
||||
svc = &service{}
|
||||
)
|
||||
|
||||
svc.SetEventRegistry(nil)
|
||||
svc.SetAuthTokenMaker(nil)
|
||||
svc.SetUserFinder(nil)
|
||||
svc.SetRoleFinder(nil)
|
||||
}
|
||||
|
||||
func TestService_canExec(t *testing.T) {
|
||||
var (
|
||||
a = assert.New(t)
|
||||
svc = &service{
|
||||
users: &mockUserSvc{user: &types.User{ID: 42, Email: "dummy@mo.ck", Handle: "dummy"}},
|
||||
roles: &mockRoleSvc{role: &types.Role{ID: 84, Handle: "role", Name: "ROLE"}},
|
||||
denyExec: make(map[string]map[uint64]bool),
|
||||
}
|
||||
|
||||
ctx = auth.SetIdentityToContext(context.Background(), auth.Authenticated(42, 84))
|
||||
|
||||
script1 = &ServerScript{
|
||||
Name: "s1",
|
||||
Triggers: []*Trigger{&Trigger{
|
||||
EventTypes: []string{onManualEventType},
|
||||
ResourceTypes: []string{"res"},
|
||||
}},
|
||||
Security: &Security{
|
||||
RunAs: "foo",
|
||||
Allow: []string{"role"},
|
||||
},
|
||||
}
|
||||
|
||||
script2 = &ServerScript{
|
||||
Name: "s2",
|
||||
Triggers: []*Trigger{&Trigger{
|
||||
EventTypes: []string{onManualEventType},
|
||||
ResourceTypes: []string{"res"},
|
||||
}},
|
||||
Security: &Security{
|
||||
RunAs: "foo",
|
||||
Deny: []string{"role"},
|
||||
},
|
||||
}
|
||||
|
||||
script3 = &ServerScript{
|
||||
Name: "s3", // permissions will not be added, not a manual script
|
||||
Security: &Security{
|
||||
RunAs: "foo",
|
||||
Deny: []string{"role"},
|
||||
},
|
||||
}
|
||||
|
||||
script4 = &ServerScript{
|
||||
Name: "s3", // should not be even added, duplicated name (hence length=3)
|
||||
Security: &Security{
|
||||
RunAs: "foo",
|
||||
Deny: []string{"role"},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
if testing.Verbose() {
|
||||
svc.log = logger.MakeDebugLogger()
|
||||
} else {
|
||||
svc.log = zap.NewNop()
|
||||
}
|
||||
|
||||
svc.registerServerScripts(ctx, script1, script2, script3, script4)
|
||||
|
||||
a.Len(svc.sScripts, 3)
|
||||
a.Len(svc.denyExec, 2)
|
||||
|
||||
a.True(svc.canExec(ctx, script1.Name))
|
||||
a.False(svc.canExec(ctx, script2.Name))
|
||||
}
|
||||
|
||||
func TestService_Exec(t *testing.T) {
|
||||
var (
|
||||
a = assert.New(t)
|
||||
svc = &service{
|
||||
explicit: map[string]map[string]bool{},
|
||||
opt: options.CorredorOpt{Enabled: true},
|
||||
}
|
||||
)
|
||||
|
||||
a.Error(svc.Exec(nil, "script", &mockEvent{eType: "not-onManual"}))
|
||||
a.Error(svc.Exec(nil, "script", &mockEvent{eType: onManualEventType}))
|
||||
svc.explicit["script"] = nil
|
||||
a.Error(svc.Exec(nil, "script", &mockEvent{eType: onManualEventType}))
|
||||
}
|
||||
47
server/pkg/corredor/type_set.gen.go
generated
Normal file
47
server/pkg/corredor/type_set.gen.go
generated
Normal file
@@ -0,0 +1,47 @@
|
||||
package corredor
|
||||
|
||||
// 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/corredor/types.yaml
|
||||
|
||||
type (
|
||||
|
||||
// ScriptSet slice of Script
|
||||
//
|
||||
// This type is auto-generated.
|
||||
ScriptSet []*Script
|
||||
)
|
||||
|
||||
// Walk iterates through every slice item and calls w(Script) err
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (set ScriptSet) Walk(w func(*Script) error) (err error) {
|
||||
for i := range set {
|
||||
if err = w(set[i]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Filter iterates through every slice item, calls f(Script) (bool, err) and return filtered slice
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (set ScriptSet) Filter(f func(*Script) (bool, error)) (out ScriptSet, err error) {
|
||||
var ok bool
|
||||
out = ScriptSet{}
|
||||
for i := range set {
|
||||
if ok, err = f(set[i]); err != nil {
|
||||
return
|
||||
} else if ok {
|
||||
out = append(out, set[i])
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
71
server/pkg/corredor/type_set.gen_test.go
generated
Normal file
71
server/pkg/corredor/type_set.gen_test.go
generated
Normal file
@@ -0,0 +1,71 @@
|
||||
package corredor
|
||||
|
||||
// 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/corredor/types.yaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestScriptSetWalk(t *testing.T) {
|
||||
var (
|
||||
value = make(ScriptSet, 3)
|
||||
req = require.New(t)
|
||||
)
|
||||
|
||||
// check walk with no errors
|
||||
{
|
||||
err := value.Walk(func(*Script) error {
|
||||
return nil
|
||||
})
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
// check walk with error
|
||||
req.Error(value.Walk(func(*Script) error { return fmt.Errorf("walk error") }))
|
||||
}
|
||||
|
||||
func TestScriptSetFilter(t *testing.T) {
|
||||
var (
|
||||
value = make(ScriptSet, 3)
|
||||
req = require.New(t)
|
||||
)
|
||||
|
||||
// filter nothing
|
||||
{
|
||||
set, err := value.Filter(func(*Script) (bool, error) {
|
||||
return true, nil
|
||||
})
|
||||
req.NoError(err)
|
||||
req.Equal(len(set), len(value))
|
||||
}
|
||||
|
||||
// filter one item
|
||||
{
|
||||
found := false
|
||||
set, err := value.Filter(func(*Script) (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(*Script) (bool, error) {
|
||||
return false, fmt.Errorf("filter error")
|
||||
})
|
||||
req.Error(err)
|
||||
}
|
||||
}
|
||||
142
server/pkg/corredor/types.go
Normal file
142
server/pkg/corredor/types.go
Normal file
@@ -0,0 +1,142 @@
|
||||
package corredor
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/eventbus"
|
||||
)
|
||||
|
||||
type (
|
||||
// ScriptSecurity sets script security and run-as flag
|
||||
//
|
||||
// Determinate user (if >0) that script will run under
|
||||
// and allow/deny combination for roles that can see & execute
|
||||
// explicit (onManual) scripts
|
||||
//
|
||||
// Please note that implicit scripts cannot have RBAC protection!
|
||||
// These scripts are always executed as configured no mather who runs them.
|
||||
ScriptSecurity struct {
|
||||
*Security
|
||||
runAs uint64
|
||||
}
|
||||
|
||||
Script struct {
|
||||
Name string `json:"name"`
|
||||
Label string `json:"label"`
|
||||
Description string `json:"description"`
|
||||
Errors []string `json:"errors,omitempty"`
|
||||
Triggers []*Trigger `json:"triggers"`
|
||||
Iterator *Iterator `json:"iterator"`
|
||||
Security *ScriptSecurity `json:"security"`
|
||||
UpdatedAt string `json:"updatedAt,omitempty"`
|
||||
|
||||
// If bundle or type is set, consider
|
||||
// this a frontend script
|
||||
Bundle string `json:"bundle,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
}
|
||||
|
||||
// allows passing extra kv with event arguments
|
||||
scriptArgs struct {
|
||||
event ScriptArgs
|
||||
extra map[string]interface{}
|
||||
}
|
||||
)
|
||||
|
||||
// FindByName returns script from the set if it exists
|
||||
func (set ScriptSet) FindByName(name string) *Script {
|
||||
for i := range set {
|
||||
if set[i].Name == name {
|
||||
return set[i]
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String fn to make logging a bit friendlier
|
||||
func (ss *ScriptSecurity) String() (o string) {
|
||||
if ss == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
|
||||
return fmt.Sprintf(
|
||||
"runAs: %s, allow: %s, deny: %s",
|
||||
ss.RunAs,
|
||||
strings.Join(ss.Allow, ","),
|
||||
strings.Join(ss.Deny, ","),
|
||||
)
|
||||
}
|
||||
|
||||
func ExtendScriptArgs(ev ScriptArgs, args map[string]interface{}) ScriptArgs {
|
||||
return &scriptArgs{
|
||||
event: ev,
|
||||
extra: args,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scriptArgs) ResourceType() string {
|
||||
return s.event.ResourceType()
|
||||
|
||||
}
|
||||
|
||||
func (s *scriptArgs) EventType() string {
|
||||
return s.event.EventType()
|
||||
}
|
||||
|
||||
func (s *scriptArgs) Match(matcher eventbus.ConstraintMatcher) bool {
|
||||
return s.event.Match(matcher)
|
||||
}
|
||||
|
||||
func (s *scriptArgs) Encode() (enc map[string][]byte, err error) {
|
||||
if enc, err = s.event.Encode(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if enc == nil {
|
||||
enc = make(map[string][]byte)
|
||||
}
|
||||
|
||||
for k, v := range s.extra {
|
||||
if enc[k] != nil {
|
||||
// skip all that were encoded
|
||||
// by event encoder
|
||||
continue
|
||||
}
|
||||
|
||||
if enc[k], err = json.Marshal(v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *scriptArgs) Decode(dec map[string][]byte) (err error) {
|
||||
if err = s.event.Decode(dec); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for k := range s.extra {
|
||||
// @todo how do we omit one decoded by event?
|
||||
raw, has := dec[k]
|
||||
if !has || len(raw) == 0 {
|
||||
// Script only returns the keys it wants to; anything it left
|
||||
// out keeps the value it was invoked with
|
||||
continue
|
||||
}
|
||||
|
||||
// Decoding into s.extra[k] directly is not possible; it holds a
|
||||
// value, not a pointer to one
|
||||
var val interface{}
|
||||
if err = json.Unmarshal(raw, &val); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.extra[k] = val
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
4
server/pkg/corredor/types.yaml
Normal file
4
server/pkg/corredor/types.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
package: corredor
|
||||
types:
|
||||
Script:
|
||||
noIdField: true
|
||||
71
server/pkg/corredor/types_test.go
Normal file
71
server/pkg/corredor/types_test.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package corredor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestScriptArgs_Decode(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
extra map[string]interface{}
|
||||
dec map[string][]byte
|
||||
want map[string]interface{}
|
||||
}{
|
||||
{
|
||||
// Corredor only returns the keys the script put on the returned
|
||||
// object, so this is the normal case, not an edge one
|
||||
name: "script returns only some of the args",
|
||||
extra: map[string]interface{}{"validated": "false", "untouched": "keep"},
|
||||
dec: map[string][]byte{"validated": []byte(`"true"`)},
|
||||
want: map[string]interface{}{"validated": "true", "untouched": "keep"},
|
||||
},
|
||||
{
|
||||
name: "script returns nothing",
|
||||
extra: map[string]interface{}{"validated": "false"},
|
||||
dec: map[string][]byte{},
|
||||
want: map[string]interface{}{"validated": "false"},
|
||||
},
|
||||
{
|
||||
name: "script returns an empty value",
|
||||
extra: map[string]interface{}{"validated": "false"},
|
||||
dec: map[string][]byte{"validated": {}},
|
||||
want: map[string]interface{}{"validated": "false"},
|
||||
},
|
||||
{
|
||||
name: "script returns another type than it was given",
|
||||
extra: map[string]interface{}{"count": "1"},
|
||||
dec: map[string][]byte{"count": []byte(`42`)},
|
||||
want: map[string]interface{}{"count": float64(42)},
|
||||
},
|
||||
{
|
||||
name: "script returns a structured value",
|
||||
extra: map[string]interface{}{"cfg": "none"},
|
||||
dec: map[string][]byte{"cfg": []byte(`{"a":true}`)},
|
||||
want: map[string]interface{}{"cfg": map[string]interface{}{"a": true}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
sa = ExtendScriptArgs(mockEvent{}, tt.extra)
|
||||
)
|
||||
|
||||
req.NoError(sa.Decode(tt.dec))
|
||||
req.Equal(tt.want, tt.extra)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestScriptArgs_DecodeMalformed(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
extra = map[string]interface{}{"validated": "false"}
|
||||
sa = ExtendScriptArgs(mockEvent{}, extra)
|
||||
)
|
||||
|
||||
req.Error(sa.Decode(map[string][]byte{"validated": []byte(`{oops`)}))
|
||||
}
|
||||
125
server/pkg/corredor/util.go
Normal file
125
server/pkg/corredor/util.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package corredor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/eventbus"
|
||||
"github.com/cortezaproject/corteza/server/pkg/slice"
|
||||
)
|
||||
|
||||
type (
|
||||
automationListSetPayload struct {
|
||||
Filter Filter `json:"filter"`
|
||||
Set []*Script `json:"set"`
|
||||
}
|
||||
)
|
||||
|
||||
// mapExplicitTriggers scans for explicit event types from the list of script's triggers
|
||||
// and returns a hash map with resources from these explicit triggers
|
||||
func mapExplicitTriggers(script *ServerScript) map[string]bool {
|
||||
var (
|
||||
hash = make(map[string]bool)
|
||||
)
|
||||
|
||||
for i := range script.Triggers {
|
||||
// We're modifying trigger in the loop,
|
||||
// so let's make a copy we can play with
|
||||
trigger := script.Triggers[i]
|
||||
|
||||
if len(slice.IntersectStrings(trigger.EventTypes, explicitEventTypes)) > 0 {
|
||||
for _, res := range trigger.ResourceTypes {
|
||||
hash[res] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hash
|
||||
}
|
||||
|
||||
// converts trigger's constraint to eventbus' constraint options
|
||||
func triggerToHandlerOps(t *Trigger) (oo []eventbus.HandlerRegOp, err error) {
|
||||
if len(t.ResourceTypes) == 0 {
|
||||
return nil, fmt.Errorf("cannot generate event handler without at least one resource")
|
||||
}
|
||||
|
||||
if len(t.EventTypes) == 0 {
|
||||
return nil, fmt.Errorf("cannot generate event handler without at least one events")
|
||||
}
|
||||
|
||||
// Make a copy of event types slice so that we do not modify it
|
||||
types := slice.PluckString(t.EventTypes, onManualEventType)
|
||||
|
||||
// If no other event types are left on the trigger,
|
||||
// no need for procede
|
||||
if len(types) == 0 && len(t.EventTypes) > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
oo = append(oo, eventbus.For(t.ResourceTypes...))
|
||||
oo = append(oo, eventbus.On(types...))
|
||||
|
||||
if cc, err := constraintsToHandlerOps(t.Constraints); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
oo = append(oo, cc...)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func constraintsToHandlerOps(cc []*TConstraint) (oo []eventbus.HandlerRegOp, err error) {
|
||||
for _, raw := range cc {
|
||||
if c, err := eventbus.ConstraintMaker(raw.Name, raw.Op, raw.Value...); err != nil {
|
||||
return nil, fmt.Errorf("cannot generate constraints: %w", err)
|
||||
} else {
|
||||
oo = append(oo, eventbus.Constraint(c))
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// encode adds entry (with json encoded value) to hash map
|
||||
// used to prepare data for transmission
|
||||
func encodeArguments(args map[string]string, key string, val interface{}) (err error) {
|
||||
var tmp []byte
|
||||
|
||||
if tmp, err = json.Marshal(val); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
args[key] = string(tmp)
|
||||
return
|
||||
}
|
||||
|
||||
// GenericListHandler returns filtered list of scripts
|
||||
func GenericListHandler(ctx context.Context, svc *service, f Filter, resourcePrefix string) (p *automationListSetPayload, err error) {
|
||||
if f.ExcludeInvalid {
|
||||
// resource prefix filtering is only applicable when we want to
|
||||
// exclude invalid scripts, because invalid scripts do not have
|
||||
// (usually, depends at what level error occurred) information
|
||||
// about triggers and resources
|
||||
f.procRTPrefixes(resourcePrefix)
|
||||
}
|
||||
|
||||
p = &automationListSetPayload{}
|
||||
p.Set, p.Filter, err = svc.Find(ctx, f)
|
||||
return p, err
|
||||
}
|
||||
|
||||
func GenericBundleHandler(ctx context.Context, svc *service, bundleName, bundleType, ext string) (interface{}, error) {
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
// Serve bundle directly for now
|
||||
bundle := svc.GetBundle(ctx, bundleName, bundleType)
|
||||
if bundle == nil {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(bundle.Code))
|
||||
}, nil
|
||||
}
|
||||
86
server/pkg/corredor/util_test.go
Normal file
86
server/pkg/corredor/util_test.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package corredor
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza/server/pkg/eventbus"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMapManualTriggers(t *testing.T) {
|
||||
var (
|
||||
a = assert.New(t)
|
||||
)
|
||||
|
||||
a.EqualValues(
|
||||
map[string]bool{
|
||||
"r1": true,
|
||||
"r2": true,
|
||||
},
|
||||
mapExplicitTriggers(&ServerScript{
|
||||
Triggers: []*Trigger{&Trigger{
|
||||
ResourceTypes: []string{"r1", "r2"},
|
||||
EventTypes: []string{"onTimestamp", onManualEventType, "onInterval"},
|
||||
}},
|
||||
}),
|
||||
)
|
||||
|
||||
a.EqualValues(
|
||||
map[string]bool{},
|
||||
mapExplicitTriggers(&ServerScript{
|
||||
Triggers: []*Trigger{&Trigger{
|
||||
ResourceTypes: []string{"r1", "r2"},
|
||||
EventTypes: []string{"onTimestamp", "onInterval"},
|
||||
}},
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func TestTriggerOptsMaking(t *testing.T) {
|
||||
var (
|
||||
a = assert.New(t)
|
||||
|
||||
trg = &Trigger{
|
||||
ResourceTypes: []string{"r1", "r2"},
|
||||
EventTypes: []string{"onTimestamp", onManualEventType, "onInterval"},
|
||||
Constraints: []*TConstraint{
|
||||
&TConstraint{Name: "some1", Op: "eq", Value: []string{"other"}},
|
||||
&TConstraint{Name: "some2", Op: "eq", Value: []string{"other"}},
|
||||
},
|
||||
}
|
||||
|
||||
oo []eventbus.HandlerRegOp
|
||||
err error
|
||||
)
|
||||
|
||||
oo, err = triggerToHandlerOps(trg)
|
||||
a.NoError(err)
|
||||
a.Len(oo, 4) // 1x all resources, 1x all events, 2x constraints
|
||||
|
||||
oo, err = triggerToHandlerOps(&Trigger{ResourceTypes: []string{"bar"}})
|
||||
a.Error(err, "expecting to fail on trigger w/o events")
|
||||
|
||||
oo, err = triggerToHandlerOps(&Trigger{EventTypes: []string{"foo"}})
|
||||
a.Error(err, "expecting to fail on trigger w/o resources")
|
||||
}
|
||||
|
||||
func TestArgEncoding(t *testing.T) {
|
||||
var (
|
||||
a = assert.New(t)
|
||||
|
||||
args = map[string]string{}
|
||||
)
|
||||
|
||||
a.NoError(encodeArguments(args, "foo", "string"))
|
||||
a.NoError(encodeArguments(args, "bar", 42))
|
||||
a.NoError(encodeArguments(args, "baz", true))
|
||||
a.NoError(encodeArguments(args, "obj", struct{ A string }{A: "A"}))
|
||||
a.Error(encodeArguments(args, "func", func() {}))
|
||||
|
||||
a.EqualValues(map[string]string{
|
||||
"foo": `"string"`,
|
||||
"bar": `42`,
|
||||
"baz": `true`,
|
||||
"obj": `{"A":"A"}`,
|
||||
}, args)
|
||||
}
|
||||
Reference in New Issue
Block a user