diff --git a/pkg/envoy/resource/base.go b/pkg/envoy/resource/base.go index fb9984b18..ead5373d9 100644 --- a/pkg/envoy/resource/base.go +++ b/pkg/envoy/resource/base.go @@ -6,6 +6,11 @@ type ( ii Identifiers rr RefSet } + + EnvoyConfig struct { + // SkipIf determines when the encoding should be skipped for this resource + SkipIf string + } ) // State management methods diff --git a/pkg/envoy/resource/compose_record.go b/pkg/envoy/resource/compose_record.go index 8e9c0e9fe..d95a9627b 100644 --- a/pkg/envoy/resource/compose_record.go +++ b/pkg/envoy/resource/compose_record.go @@ -21,6 +21,8 @@ type ( Values map[string]string SysValues *rawSysValues RefUsers map[string]string + + Config *EnvoyConfig } ComposeRecordRawSet []*ComposeRecordRaw diff --git a/pkg/envoy/store/compose_record.go b/pkg/envoy/store/compose_record.go index 8bf5c83b5..bde6d8caf 100644 --- a/pkg/envoy/store/compose_record.go +++ b/pkg/envoy/store/compose_record.go @@ -9,6 +9,7 @@ import ( "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/pkg/envoy" "github.com/cortezaproject/corteza-server/pkg/envoy/resource" + "github.com/cortezaproject/corteza-server/pkg/filter" "github.com/cortezaproject/corteza-server/store" ) @@ -20,6 +21,9 @@ type ( relNS *types.Namespace relMod *types.Module + + // Little helper flag for conditional encoding + modEmpty bool } ) @@ -46,6 +50,7 @@ func (n *composeRecordState) Prepare(ctx context.Context, s store.Storer, state return composeNamespaceErrUnresolved(n.res.NsRef.Identifiers) } + n.modEmpty = true n.relMod = findComposeModuleR(state.ParentResources, n.res.ModRef.Identifiers) if n.relMod == nil && n.relNS.ID > 0 { n.relMod, err = findComposeModuleS(ctx, s, n.relNS.ID, makeGenericFilter(n.res.ModRef.Identifiers)) @@ -58,6 +63,17 @@ func (n *composeRecordState) Prepare(ctx context.Context, s store.Storer, state if err != nil { return err } + + // Check if empty + rr, _, err := store.SearchComposeRecords(ctx, s, n.relMod, types.RecordFilter{ + ModuleID: n.relMod.ID, + NamespaceID: n.relNS.ID, + Paging: filter.Paging{Limit: 1}, + }) + if err != nil && err != store.ErrNotFound { + return err + } + n.modEmpty = len(rr) == 0 } } @@ -129,6 +145,30 @@ func (n *composeRecordState) Encode(ctx context.Context, s store.Storer, state * im := n.res.IDMap return n.res.Walker(func(r *resource.ComposeRecordRaw) error { + // So we don't have to worry about nil + cfg := r.Config + if cfg == nil { + cfg = &resource.EnvoyConfig{} + } + + if cfg.SkipIf != "" { + evl, err := exprP.NewEvaluable(cfg.SkipIf) + if err != nil { + return err + } + // @todo expand this + skip, err := evl.EvalBool(ctx, map[string]interface{}{ + "empty": n.modEmpty, + }) + if err != nil { + return err + } + + if skip { + return nil + } + } + // Simple wrapper to do some post-processing steps dfr := func(err error) error { if n.cfg.Defer != nil { diff --git a/pkg/envoy/store/util.go b/pkg/envoy/store/util.go index b4c935add..60560637a 100644 --- a/pkg/envoy/store/util.go +++ b/pkg/envoy/store/util.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/cortezaproject/corteza-server/pkg/envoy/resource" + "github.com/cortezaproject/corteza-server/pkg/expr" "github.com/cortezaproject/corteza-server/pkg/handle" "github.com/cortezaproject/corteza-server/pkg/id" ) @@ -26,6 +27,8 @@ var ( NextID = func() uint64 { return id.Next() } + + exprP = expr.Parser() ) // makeGenericFilter is a helper to determine the base resource filter. diff --git a/pkg/envoy/yaml/compose_record.go b/pkg/envoy/yaml/compose_record.go index b5c563ba5..f5fa386b7 100644 --- a/pkg/envoy/yaml/compose_record.go +++ b/pkg/envoy/yaml/compose_record.go @@ -13,6 +13,8 @@ type ( values map[string]string sysValues map[string]string + eCfg *resource.EnvoyConfig + refModule string refNamespace string // createdBy, updatedBy, deletedBy, ownedBy @@ -95,7 +97,8 @@ func (wset composeRecordSet) MarshalEnvoy() ([]resource.Interface, error) { r := &resource.ComposeRecordRaw{ // @todo change this probably - ID: res.values["id"], + ID: res.values["id"], + Config: res.eCfg, } r.ApplyValues(res.values) r.ApplyValues(res.sysValues) @@ -153,6 +156,10 @@ func (wrap *composeRecord) UnmarshalYAML(n *yaml.Node) (err error) { // return //} + if wrap.eCfg, err = decodeEnvoyConfig(n); err != nil { + return + } + return eachMap(n, func(k, v *yaml.Node) error { switch k.Value { case "module": diff --git a/pkg/envoy/yaml/resource.go b/pkg/envoy/yaml/resource.go new file mode 100644 index 000000000..ccc4d674d --- /dev/null +++ b/pkg/envoy/yaml/resource.go @@ -0,0 +1,31 @@ +package yaml + +import ( + "github.com/cortezaproject/corteza-server/pkg/envoy/resource" + "gopkg.in/yaml.v3" +) + +func decodeEnvoyConfig(n *yaml.Node) (*resource.EnvoyConfig, error) { + ec := &resource.EnvoyConfig{} + + var ecNode *yaml.Node + for i, k := range n.Content { + if k.Value == "(envoy)" { + ecNode = n.Content[i+1] + break + } + } + + if ecNode == nil { + return nil, nil + } + + return ec, eachMap(ecNode, func(k, v *yaml.Node) (err error) { + switch k.Value { + case "skipIf", "skip": + return decodeScalar(v, "decode skip if", &ec.SkipIf) + } + + return nil + }) +} diff --git a/tests/envoy/main.go b/tests/envoy/main.go index f45dd7290..1777ad930 100644 --- a/tests/envoy/main.go +++ b/tests/envoy/main.go @@ -2,6 +2,7 @@ package envoy import ( "context" + "fmt" "os" "path" @@ -127,6 +128,37 @@ func storeModuleField(ctx context.Context, s store.Storer, modID, fieldID uint64 return store.CreateComposeModuleField(ctx, s, f) } +func storeRecord(ctx context.Context, s store.Storer, nsID, moduleID, recordID uint64, vv ...string) error { + r := &types.Record{ + ID: recordID, + ModuleID: moduleID, + NamespaceID: nsID, + Values: make(types.RecordValueSet, 0, len(vv)), + } + + mod := &types.Module{ + ID: moduleID, + NamespaceID: nsID, + Fields: make(types.ModuleFieldSet, 0, len(vv)), + } + + for i, v := range vv { + r.Values = append(r.Values, &types.RecordValue{ + RecordID: recordID, + Name: fmt.Sprintf("f%d", i+1), + Value: v, + }) + + mod.Fields = append(mod.Fields, &types.ModuleField{ + ModuleID: moduleID, + Kind: "String", + Name: fmt.Sprintf("f%d", i+1), + }) + } + + return store.CreateComposeRecord(ctx, s, mod, r) +} + func storeRole(ctx context.Context, s store.Storer, rID uint64, ss ...string) error { r := &stypes.Role{ ID: rID, diff --git a/tests/envoy/simple_test.go b/tests/envoy/simple_test.go index 9d1198bac..9b08ccbdd 100644 --- a/tests/envoy/simple_test.go +++ b/tests/envoy/simple_test.go @@ -513,6 +513,53 @@ func TestSimpleCases(t *testing.T) { req.Equal("v1", v.Value) }, }, + + { + name: "simple records; conditional", + suite: "simple", + file: "records_conditional", + pre: func() (err error) { + return ce( + s.TruncateComposeRecords(ctx, nil), + s.TruncateComposeModuleFields(ctx), + s.TruncateComposeModules(ctx), + s.TruncateComposeNamespaces(ctx), + + storeNamespace(ctx, s, 100, "ns1"), + + storeModule(ctx, s, 100, 200, "mod1"), + storeModuleField(ctx, s, 200, 300, "f1"), + storeRecord(ctx, s, 100, 200, 400, "existing value"), + + storeModule(ctx, s, 100, 201, "mod2"), + storeModuleField(ctx, s, 201, 301, "f1"), + ) + }, + post: func(req *require.Assertions, err error) { + req.NoError(err) + }, + check: func(req *require.Assertions) { + mod1, err := store.LookupComposeModuleByID(ctx, s, 200) + req.NoError(err) + req.NotNil(mod1) + + mod2, err := store.LookupComposeModuleByID(ctx, s, 201) + req.NoError(err) + req.NotNil(mod2) + + rr1, _, err := store.SearchComposeRecords(ctx, s, mod1, types.RecordFilter{ModuleID: mod1.ID, NamespaceID: mod1.NamespaceID}) + req.NoError(err) + req.NotNil(rr1) + req.Len(rr1, 1) + req.Equal("existing value", rr1[0].Values.FilterByName("f1")[0].Value) + + rr2, _, err := store.SearchComposeRecords(ctx, s, mod2, types.RecordFilter{ModuleID: mod2.ID, NamespaceID: mod2.NamespaceID}) + req.NoError(err) + req.NotNil(rr2) + req.Len(rr2, 1) + req.Equal("f1 value", rr2[0].Values.FilterByName("f1")[0].Value) + }, + }, } for _, c := range cases { diff --git a/tests/envoy/testdata/simple/records_conditional.yaml b/tests/envoy/testdata/simple/records_conditional.yaml new file mode 100644 index 000000000..d86c2143e --- /dev/null +++ b/tests/envoy/testdata/simple/records_conditional.yaml @@ -0,0 +1,13 @@ +namespace: ns1 +records: + mod1: + - (envoy): + skipIf: "!empty" + values: + f1: f1 value + + mod2: + - (envoy): + skipIf: "!empty" + values: + f1: f1 value