diff --git a/pkg/envoy/directory/decode.go b/pkg/envoy/directory/decode.go index d108f0d00..59c2c21f6 100644 --- a/pkg/envoy/directory/decode.go +++ b/pkg/envoy/directory/decode.go @@ -53,6 +53,11 @@ func Decode(ctx context.Context, p string, decoders ...decoder) ([]resource.Inte for _, d := range decoders { if !d.CanDecodeFile(f) { // decoder can not handle this file + // Make sure to reset it, as the above check consumes the reader + if _, err = f.Seek(0, 0); err != nil { + return err + } + continue } diff --git a/pkg/envoy/yaml/compose_module.go b/pkg/envoy/yaml/compose_module.go index a524cabe2..f8382e627 100644 --- a/pkg/envoy/yaml/compose_module.go +++ b/pkg/envoy/yaml/compose_module.go @@ -2,6 +2,7 @@ package yaml import ( "fmt" + "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/pkg/envoy" "github.com/cortezaproject/corteza-server/pkg/envoy/resource" @@ -32,18 +33,11 @@ type ( composeModuleSet []*composeModule composeModuleField struct { - res *types.ModuleField `yaml:",inline"` + res *types.ModuleField rbac rbacRuleSet } composeModuleFieldSet []*composeModuleField - // aux struct to help us with decoding of module field - composeModuleFieldAux struct { - *types.ModuleField - - Expressions *composeModuleFieldExprAux - } - // aux struct for decoding module field expressions composeModuleFieldExprAux types.ModuleFieldExpr ) @@ -273,23 +267,10 @@ func (set composeModuleFieldSet) set() (out types.ModuleFieldSet) { func (wrap *composeModuleField) UnmarshalYAML(n *yaml.Node) (err error) { if wrap.res == nil { + wrap.rbac = make(rbacRuleSet, 0, 10) wrap.res = &types.ModuleField{} } - // cast existing field expressions struct to aux struct - exprAux := composeModuleFieldExprAux(wrap.res.Expressions) - - // construct field aux struct from resource and aux expr struct - // this way yaml pkg can help us with field unmarshalling but - // we catch expressions and do our own magic - aux := &composeModuleFieldAux{ModuleField: wrap.res, Expressions: &exprAux} - if err = n.Decode(aux); err != nil { - return - } - - // cast unmarshalled expressions back to original type - wrap.res.Expressions = types.ModuleFieldExpr(exprAux) - if wrap.rbac, err = decodeRbac(n); err != nil { return } @@ -299,6 +280,39 @@ func (wrap *composeModuleField) UnmarshalYAML(n *yaml.Node) (err error) { case "name": return fmt.Errorf("name should be encoded as field definition key") + case "place": + return decodeScalar(v, "module place", &wrap.res.Place) + + case "kind": + return decodeScalar(v, "module kind", &wrap.res.Kind) + + case "label": + return decodeScalar(v, "module label", &wrap.res.Label) + + case "private": + return decodeScalar(v, "module private", &wrap.res.Private) + + case "required": + return decodeScalar(v, "module required", &wrap.res.Required) + + case "visible": + return decodeScalar(v, "module visible", &wrap.res.Visible) + + case "multi": + return decodeScalar(v, "module multi", &wrap.res.Multi) + + case "options": + if err = v.Decode(&wrap.res.Options); err != nil { + return err + } + + case "expressions": + ea := composeModuleFieldExprAux{} + if err = v.Decode(&ea); err != nil { + return err + } + wrap.res.Expressions = types.ModuleFieldExpr(ea) + case "default": var rvs = types.RecordValueSet{} switch v.Kind { diff --git a/tests/envoy/main.go b/tests/envoy/main.go index 139750708..f45dd7290 100644 --- a/tests/envoy/main.go +++ b/tests/envoy/main.go @@ -149,3 +149,14 @@ func ce(ee ...error) error { } return nil } + +func fullModLoad(ctx context.Context, s store.Storer, req *require.Assertions, nsID uint64, handle string) (*types.Module, error) { + mod, err := store.LookupComposeModuleByNamespaceIDHandle(ctx, s, nsID, handle) + req.NoError(err) + req.NotNil(mod) + + mod.Fields, _, err = store.SearchComposeModuleFields(ctx, s, types.ModuleFieldFilter{ModuleID: []uint64{mod.ID}}) + req.NoError(err) + req.NotNil(mod.Fields) + return mod, err +} diff --git a/tests/envoy/mod_expr_test.go b/tests/envoy/mod_expr_test.go new file mode 100644 index 000000000..3a62272a8 --- /dev/null +++ b/tests/envoy/mod_expr_test.go @@ -0,0 +1,91 @@ +package envoy + +import ( + "context" + "testing" + + "github.com/cortezaproject/corteza-server/pkg/envoy/resource" + su "github.com/cortezaproject/corteza-server/pkg/envoy/store" + "github.com/cortezaproject/corteza-server/store" + "github.com/stretchr/testify/require" +) + +func TestModuleExpr(t *testing.T) { + var ( + ctx = context.Background() + s, err = initStore(ctx) + ) + if err != nil { + t.Fatalf("failed to init sqlite in-memory db: %v", err) + } + + ni := uint64(0) + su.NextID = func() uint64 { + ni++ + return ni + } + + prepare := func(ctx context.Context, s store.Storer, t *testing.T, suite string) (*require.Assertions, error) { + req := require.New(t) + + nn, err := dd(ctx, suite) + req.NoError(err) + + crs := resource.ComposeRecordShaper() + nn, err = resource.Shape(nn, crs) + req.NoError(err) + + return req, encode(ctx, s, nn) + } + // Prepare + s, err = initStore(ctx) + err = ce( + err, + + s.TruncateRbacRules(ctx), + s.TruncateRoles(ctx), + s.TruncateActionlogs(ctx), + s.TruncateApplications(ctx), + s.TruncateAttachments(ctx), + s.TruncateComposeAttachments(ctx), + s.TruncateComposeCharts(ctx), + s.TruncateComposeNamespaces(ctx), + s.TruncateComposeModules(ctx), + s.TruncateComposeModuleFields(ctx), + s.TruncateComposePages(ctx), + + storeRole(ctx, s, 1, "everyone"), + storeRole(ctx, s, 2, "admins"), + ) + if err != nil { + t.Fatal(err.Error()) + } + + req, err := prepare(ctx, s, t, "mod_expr") + req.NoError(err) + + t.Run("module field expressions", func(t *testing.T) { + ns, err := store.LookupComposeNamespaceBySlug(ctx, s, "crm") + req.NoError(err) + req.NotNil(ns) + + mod, err := fullModLoad(ctx, s, req, ns.ID, "Account") + req.NotNil(mod) + req.NoError(err) + req.Len(mod.Fields, 2) + + // Check the full thing + mfF := mod.Fields[0] + req.Equal("a > b", mfF.Expressions.ValueExpr) + req.Subset(mfF.Expressions.Sanitizers, []string{"trim(value)"}) + v := mfF.Expressions.Validators[0] + req.Equal("a == \"\"", v.Test) + req.Equal("Value should not be empty", v.Error) + + // Check the other validator form + mfV := mod.Fields[1] + v = mfV.Expressions.Validators[0] + req.Equal("value == \"\"", v.Test) + req.Equal("Value should be filled", v.Error) + }) +} diff --git a/tests/envoy/provision_batch_test.go b/tests/envoy/provision_batch_test.go index 176930ef3..2ce62c7b8 100644 --- a/tests/envoy/provision_batch_test.go +++ b/tests/envoy/provision_batch_test.go @@ -44,6 +44,8 @@ func TestProvision_batch(t *testing.T) { err = ce( err, + s.TruncateRoles(ctx), + s.TruncateRbacRules(ctx), s.TruncateActionlogs(ctx), s.TruncateApplications(ctx), s.TruncateAttachments(ctx), @@ -188,14 +190,3 @@ func checkBatchProvision(ctx context.Context, t *testing.T, req *require.Asserti req.Equal(fmt.Sprintf("%s value", slug), v.Value) }) } - -func fullModLoad(ctx context.Context, s store.Storer, req *require.Assertions, nsID uint64, handle string) (*types.Module, error) { - mod, err := store.LookupComposeModuleByNamespaceIDHandle(ctx, s, nsID, handle) - req.NoError(err) - req.NotNil(mod) - - mod.Fields, _, err = store.SearchComposeModuleFields(ctx, s, types.ModuleFieldFilter{ModuleID: []uint64{mod.ID}}) - req.NoError(err) - req.NotNil(mod.Fields) - return mod, err -} diff --git a/tests/envoy/testdata/mod_expr/mod1.yaml b/tests/envoy/testdata/mod_expr/mod1.yaml new file mode 100644 index 000000000..3d6daaf3d --- /dev/null +++ b/tests/envoy/testdata/mod_expr/mod1.yaml @@ -0,0 +1,20 @@ +namespaces: + crm: + name: CRM + +modules: + Account: + name: Expressions + fields: + full: + expressions: + valueExpr: "a > b" + sanitizers: [ "trim(value)" ] + validators: + - test: 'a == ""' + error: Value should not be empty + disableDefaultValidators: true + validatorKV: + expressions: + validators: + 'value == ""': Value should be filled