From 70a61597bce7bf9e6fd5e3432f45ccf548df7d0d Mon Sep 17 00:00:00 2001 From: Denis Arh Date: Thu, 22 Oct 2020 14:04:15 +0200 Subject: [PATCH] Rework envoy node marshaling --- pkg/envoy/compose_module_node.go | 40 ++++--- pkg/envoy/compose_namespace_node.go | 4 +- pkg/envoy/compose_record_node.go | 54 ++++----- pkg/envoy/marshaler.go | 33 ++++++ pkg/envoy/store/encoder.go | 106 ++++++++--------- pkg/envoy/yaml/compose.go | 66 ++++++++++- pkg/envoy/yaml/compose_module.go | 71 +++++++++--- pkg/envoy/yaml/compose_namespace.go | 68 +++++++++-- pkg/envoy/yaml/compose_namespace_test.go | 25 ++-- pkg/envoy/yaml/compose_record.go | 50 ++++++-- pkg/envoy/yaml/compose_test.go | 7 +- pkg/envoy/yaml/decoder.go | 107 +++++++++--------- pkg/envoy/yaml/doc.go | 12 +- pkg/envoy/yaml/rbac.go | 52 ++++++++- .../yaml/testdata/compose_namespace_2.yaml | 10 -- pkg/envoy/yaml/yaml.go | 4 +- 16 files changed, 482 insertions(+), 227 deletions(-) create mode 100644 pkg/envoy/marshaler.go delete mode 100644 pkg/envoy/yaml/testdata/compose_namespace_2.yaml diff --git a/pkg/envoy/compose_module_node.go b/pkg/envoy/compose_module_node.go index c841b1ad5..daecadd05 100644 --- a/pkg/envoy/compose_module_node.go +++ b/pkg/envoy/compose_module_node.go @@ -9,26 +9,27 @@ import ( type ( // ComposeModuleNode represents a ComposeModule ComposeModuleNode struct { - Mod *types.Module + Module *types.Module // Related namespace - Ns *types.Namespace + RefNamespaceSlug string + RefNamespaceID uint64 } ) func (n *ComposeModuleNode) Identifiers() NodeIdentifiers { ii := make(NodeIdentifiers, 0) - if n.Mod.Handle != "" { - ii = ii.Add(n.Mod.Handle) + if n.Module.Handle != "" { + ii = ii.Add(n.Module.Handle) } - if n.Mod.Name != "" { - ii = ii.Add(n.Mod.Name) + if n.Module.Name != "" { + ii = ii.Add(n.Module.Name) } - if n.Mod.ID > 0 { - ii = ii.Add(strconv.FormatUint(n.Mod.ID, 10)) + if n.Module.ID > 0 { + ii = ii.Add(strconv.FormatUint(n.Module.ID, 10)) } return ii @@ -51,19 +52,17 @@ func (n *ComposeModuleNode) Relations() NodeRelationships { // Related namespace nsr := types.NamespaceRBACResource.String() - if n.Ns.Slug != "" { - rel.Add(nsr, n.Ns.Slug) + if n.RefNamespaceSlug != "" { + rel.Add(nsr, n.RefNamespaceSlug) } - if n.Ns.Name != "" { - rel.Add(nsr, n.Ns.Name) - } - if n.Ns.ID > 0 { - rel.Add(nsr, strconv.FormatUint(n.Ns.ID, 10)) + + if n.RefNamespaceID > 0 { + rel.Add(nsr, strconv.FormatUint(n.RefNamespaceID, 10)) } // Related modules via Record module fields mdr := types.ModuleRBACResource.String() - for _, f := range n.Mod.Fields { + for _, f := range n.Module.Fields { // @todo should a missing module property raise an error? if f.Kind == "Record" && f.Options.String("module") != "" { rel.Add(mdr, f.Options.String("module")) @@ -86,25 +85,24 @@ func (n *ComposeModuleNode) updateNamespace(m Node) { } mn := m.(*ComposeNamespaceNode) - n.Ns = mn.Ns + n.Module.NamespaceID = mn.Ns.ID } func (n *ComposeModuleNode) updateRecFields(m Node) { if m.Resource() != types.ModuleRBACResource.String() { return } + mn := m.(*ComposeModuleNode) // Check what record module field we can link this to - for _, f := range n.Mod.Fields { + for _, f := range n.Module.Fields { if f.Kind != "Record" { continue } if mn.Identifiers().HasAny(f.Options.String("module")) { - f.Options["module"] = strconv.FormatUint(mn.Mod.ID, 10) + f.Options["module"] = strconv.FormatUint(mn.Module.ID, 10) } } - - n.Ns = mn.Ns } diff --git a/pkg/envoy/compose_namespace_node.go b/pkg/envoy/compose_namespace_node.go index 909381fad..6f34ec181 100644 --- a/pkg/envoy/compose_namespace_node.go +++ b/pkg/envoy/compose_namespace_node.go @@ -17,9 +17,7 @@ func (n *ComposeNamespaceNode) Identifiers() NodeIdentifiers { if n.Ns.Slug != "" { ii = ii.Add(n.Ns.Slug) } - if n.Ns.Name != "" { - ii = ii.Add(n.Ns.Name) - } + if n.Ns.ID > 0 { ii = ii.Add(strconv.FormatUint(n.Ns.ID, 10)) } diff --git a/pkg/envoy/compose_record_node.go b/pkg/envoy/compose_record_node.go index 838ae4533..e8c6628e1 100644 --- a/pkg/envoy/compose_record_node.go +++ b/pkg/envoy/compose_record_node.go @@ -11,13 +11,13 @@ type ( ComposeRecordNode struct { Walk RecordIterator - // Metafields for relationship management - Mod *types.Module + RefModuleHandle string } ) func (n *ComposeRecordNode) Identifiers() NodeIdentifiers { - return identifiersForModule(n.Mod) + //return identifiersForModule(n.Mod) + return nil } func (n *ComposeRecordNode) Resource() string { @@ -36,29 +36,29 @@ func (n *ComposeRecordNode) Relations() NodeRelationships { // This omits the namespace rel. as it's transitively implied via the modules rel := make(NodeRelationships) - mdr := types.ModuleRBACResource.String() - rrr := "compose:record:" + //mdr := types.ModuleRBACResource.String() + //rrr := "compose:record:" + // + //if n.Mod == nil { + // return rel + //} - if n.Mod == nil { - return rel - } - - // Original module - mIdentifiers := identifiersForModule(n.Mod) - rel.Add(mdr, mIdentifiers...) - - // Field relationships - for _, f := range n.Mod.Fields { - if f.Kind == "Record" { - modID := f.Options.String("module") - // For the module - rel.Add(mdr, modID) - - // For the records. - // Since this record depends on another module, it also depends on those records. - rel.Add(rrr, modID) - } - } + //// Original module + //mIdentifiers := identifiersForModule(n.Mod) + //rel.Add(mdr, mIdentifiers...) + // + //// Field relationships + //for _, f := range n.Mod.Fields { + // if f.Kind == "Record" { + // modID := f.Options.String("module") + // // For the module + // rel.Add(mdr, modID) + // + // // For the records. + // // Since this record depends on another module, it also depends on those records. + // rel.Add(rrr, modID) + // } + //} return rel } @@ -69,8 +69,8 @@ func (n *ComposeRecordNode) Update(mm ...Node) { case types.ModuleRBACResource.String(): mn, _ := m.(*ComposeModuleNode) // Direct module dependency - if n.Matches(n.Resource(), identifiersForModule(mn.Mod)...) { - n.Mod = mn.Mod + if n.Matches(n.Resource(), identifiersForModule(mn.Module)...) { + //n.Mod = mn.Module } } } diff --git a/pkg/envoy/marshaler.go b/pkg/envoy/marshaler.go new file mode 100644 index 000000000..08d779203 --- /dev/null +++ b/pkg/envoy/marshaler.go @@ -0,0 +1,33 @@ +package envoy + +import "fmt" + +type ( + Marshaller interface { + MarshalEnvoy() ([]Node, error) + } +) + +// MarshalMerge takes one or more nodes and Marshals and merges all nodes +// +// +func Merge(nn []Node, ii ...interface{}) ([]Node, error) { + for _, i := range ii { + switch c := i.(type) { + case Node: + nn = append(nn, c) + + case Marshaller: + if tmp, err := c.(Marshaller).MarshalEnvoy(); err != nil { + println(err) + return nil, err + } else { + tmp = append(nn, tmp...) + } + default: + return nil, fmt.Errorf("failed to merge %T; expecting Node or Marshaller interface", i) + } + } + + return nn, nil +} diff --git a/pkg/envoy/store/encoder.go b/pkg/envoy/store/encoder.go index d046d0756..cc448d77b 100644 --- a/pkg/envoy/store/encoder.go +++ b/pkg/envoy/store/encoder.go @@ -26,30 +26,28 @@ func NewStoreEncoder(s store.Storer) *StoreEncoder { } } -func (se *StoreEncoder) Encode(ctx context.Context, s store.Storer, nn ...envoy.Node) error { - return store.Tx(ctx, se.s, func(ctx context.Context, s store.Storer) error { +func (se *StoreEncoder) Encode(ctx context.Context, nn ...envoy.Node) error { + return store.Tx(ctx, se.s, func(ctx context.Context, s store.Storer) (err error) { for _, n := range nn { switch n.Resource() { case types.NamespaceRBACResource.String(): ns := n.(*envoy.ComposeNamespaceNode) - _, err := se.encodeNamespace(ctx, s, ns) - if err != nil { - return err + if ns.Ns, err = se.encodeNamespace(ctx, s, ns); err != nil { + return } case types.ModuleRBACResource.String(): mod := n.(*envoy.ComposeModuleNode) - _, err := se.encodeModule(ctx, s, mod) - if err != nil { - return err + if mod.Module, err = se.encodeModule(ctx, s, mod); err != nil { + return } - case "compose:record:": - rec := n.(*envoy.ComposeRecordNode) - err := se.encodeRecord(ctx, s, rec) - if err != nil { - return err - } + //case "compose:record:": + // rec := n.(*envoy.ComposeRecordNode) + // + // if err = se.encodeRecord(ctx, s, rec); err != nil { + // return + // } } } @@ -60,6 +58,8 @@ func (se *StoreEncoder) Encode(ctx context.Context, s store.Storer, nn ...envoy. func (se *StoreEncoder) encodeNamespace(ctx context.Context, s store.Storer, n *envoy.ComposeNamespaceNode) (*types.Namespace, error) { cns := n.Ns + // @todo this should probably be refactored (together with services) + // so that core logic is handled in one place cns.ID = nextID() if cns.CreatedAt.IsZero() { cns.CreatedAt = time.Now() @@ -73,11 +73,11 @@ func (se *StoreEncoder) encodeNamespace(ctx context.Context, s store.Storer, n * } func (se *StoreEncoder) encodeModule(ctx context.Context, s store.Storer, m *envoy.ComposeModuleNode) (*types.Module, error) { - mod := m.Mod - cns := m.Ns + mod := m.Module + // @todo this should probably be refactored (together with services) + // so that core logic is handled in one place mod.ID = nextID() - mod.NamespaceID = cns.ID if mod.CreatedAt.IsZero() { mod.CreatedAt = time.Now() @@ -104,40 +104,44 @@ func (se *StoreEncoder) encodeModule(ctx context.Context, s store.Storer, m *env return mod, nil } -func (se *StoreEncoder) encodeRecord(ctx context.Context, s store.Storer, m *envoy.ComposeRecordNode) error { - mod := m.Mod - - return m.Walk(func(rec *types.Record) error { - rec.ID = nextID() - rec.ModuleID = mod.ID - rec.NamespaceID = mod.NamespaceID - - if rec.CreatedAt.IsZero() { - rec.CreatedAt = time.Now() - } - - rec.Values = make(types.RecordValueSet, 0, 100) - - // Process record values - for _, crv := range rec.Values { - crv.RecordID = rec.ID - rec.Values = append(rec.Values, crv) - } - rec.Values.SetUpdatedFlag(true) - rec.Values = se.setDefaultComposeRecordValues(mod, rec.Values) - rec.Values = rvSanitizer.Run(mod, rec.Values) - err := rvValidator.Run(ctx, s, mod, rec) - if err != nil { - return err - } - - if err := store.CreateComposeRecord(ctx, s, m.Mod, rec); err != nil { - return err - } - - return nil - }) -} +//func (se *StoreEncoder) encodeRecord(ctx context.Context, s store.Storer, m *envoy.ComposeRecordNode) error { +// mod := m.Mod +// +// // @todo a bit less ad-hoc-ish solution +// return m.Walk(func(rec *types.Record) error { +// // @todo this should probably be refactored (together with services) +// // so that core logic is handled in one place +// +// rec.ID = nextID() +// rec.ModuleID = mod.ID +// rec.NamespaceID = mod.NamespaceID +// +// if rec.CreatedAt.IsZero() { +// rec.CreatedAt = time.Now() +// } +// +// rec.Values = make(types.RecordValueSet, 0, 100) +// +// // Process record values +// for _, crv := range rec.Values { +// crv.RecordID = rec.ID +// rec.Values = append(rec.Values, crv) +// } +// rec.Values.SetUpdatedFlag(true) +// rec.Values = se.setDefaultComposeRecordValues(mod, rec.Values) +// rec.Values = rvSanitizer.Run(mod, rec.Values) +// err := rvValidator.Run(ctx, s, mod, rec) +// if err != nil { +// return err +// } +// +// if err := store.CreateComposeRecord(ctx, s, m.Mod, rec); err != nil { +// return err +// } +// +// return nil +// }) +//} // @note this method is coppied over from the compose/service/record. // Would it be better to unify the two methods? diff --git a/pkg/envoy/yaml/compose.go b/pkg/envoy/yaml/compose.go index 550a60b28..02ca82ac9 100644 --- a/pkg/envoy/yaml/compose.go +++ b/pkg/envoy/yaml/compose.go @@ -1,6 +1,7 @@ package yaml import ( + "github.com/cortezaproject/corteza-server/pkg/envoy" "gopkg.in/yaml.v3" ) @@ -20,19 +21,74 @@ func (c *compose) UnmarshalYAML(n *yaml.Node) error { return nodeErr(n, "expecting mapping node") } - return iterator(n, func(k, v *yaml.Node) error { + var ( + nsRef string + err error + ) + + // 1st pass: handle doc-level references + err = iterator(n, func(k, v *yaml.Node) error { switch k.Value { case "namespace": - c.namespaces = ComposeNamespaceSet{&ComposeNamespace{}} - return v.Decode(&c.namespaces[0]) + if def := findKeyNode(n, "namespaces"); def != nil { + return nodeErr(def, "cannot combine namespace reference and namespaces definition") + } + + if err := decodeScalar(v, "namespace ref", &nsRef); err != nil { + return err + } + } + + return nil + }) + + if err != nil { + return err + } + + // 2nd pass: handle definitions + return iterator(n, func(k, v *yaml.Node) error { + switch k.Value { case "namespaces": return v.Decode(&c.namespaces) + case "modules": - return v.Decode(&c.modules) + if err = v.Decode(&c.modules); err != nil { + return err + } + + return c.modules.setNamespaceRef(nsRef) + case "records": - return v.Decode(&c.records) + if err = v.Decode(&c.records); err != nil { + return err + } + + return c.records.setNamespaceRef(nsRef) } return nil }) } + +func (wrap compose) MarshalEnvoy() ([]envoy.Node, error) { + nn := make([]envoy.Node, 0, 100) + + if wrap.namespaces != nil { + if tmp, err := wrap.namespaces.MarshalEnvoy(); err != nil { + return nil, err + } else { + nn = append(nn, tmp...) + } + } + + if wrap.modules != nil { + if tmp, err := wrap.modules.MarshalEnvoy(); err != nil { + return nil, err + } else { + nn = append(nn, tmp...) + } + } + + return nn, nil +} diff --git a/pkg/envoy/yaml/compose_module.go b/pkg/envoy/yaml/compose_module.go index c566d955b..9b17bf138 100644 --- a/pkg/envoy/yaml/compose_module.go +++ b/pkg/envoy/yaml/compose_module.go @@ -3,25 +3,27 @@ package yaml import ( "fmt" "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/envoy" "github.com/cortezaproject/corteza-server/pkg/handle" "gopkg.in/yaml.v3" ) type ( ComposeModule struct { - res *types.Module - Rbac `yaml:",inline"` + res *types.Module + refNamespace string + *rbacRules } ComposeModuleSet []*ComposeModule ComposeModuleField struct { - res *types.ModuleField `yaml:",inline"` - Rbac `yaml:",inline"` + res *types.ModuleField `yaml:",inline"` + *rbacRules } ComposeModuleFieldSet []*ComposeModuleField ) -func (set *ComposeModuleSet) UnmarshalYAML(n *yaml.Node) error { +func (wset *ComposeModuleSet) UnmarshalYAML(n *yaml.Node) error { return iterator(n, func(k, v *yaml.Node) (err error) { var ( wrap = &ComposeModule{} @@ -48,11 +50,38 @@ func (set *ComposeModuleSet) UnmarshalYAML(n *yaml.Node) error { wrap.res.Name = k.Value } - *set = append(*set, wrap) + *wset = append(*wset, wrap) return }) } +func (wset ComposeModuleSet) setNamespaceRef(ref string) error { + for _, res := range wset { + if res.refNamespace != "" && ref != res.refNamespace { + return fmt.Errorf("cannot override namespace reference %s with %s", res.refNamespace, ref) + } + + res.refNamespace = ref + } + + return nil +} + +func (wset ComposeModuleSet) MarshalEnvoy() ([]envoy.Node, error) { + // namespace usually have bunch of sub-resources defined + nn := make([]envoy.Node, 0, len(wset)*10) + + for _, res := range wset { + if tmp, err := res.MarshalEnvoy(); err != nil { + return nil, err + } else { + nn = append(nn, tmp...) + } + } + + return nn, nil +} + func (wrap *ComposeModule) UnmarshalYAML(n *yaml.Node) error { if wrap.res == nil { wrap.res = &types.Module{} @@ -65,7 +94,8 @@ func (wrap *ComposeModule) UnmarshalYAML(n *yaml.Node) error { return iterator(n, func(k, v *yaml.Node) (err error) { switch k.Value { case "name": - return decodeScalar(v, &wrap.res.Name) + return decodeScalar(v, "module name", &wrap.res.Name) + case "fields": if !isKind(v, yaml.MappingNode) { return nodeErr(n, "field definition must be a map") @@ -81,12 +111,23 @@ func (wrap *ComposeModule) UnmarshalYAML(n *yaml.Node) error { wrap.res.Fields = aux.set() return nil + + case "allow", "deny": + return decodeAccessRoleOps(wrap.rbacRules, types.ModuleRBACResource, k, v) + } return nil }) } +func (wrap ComposeModule) MarshalEnvoy() ([]envoy.Node, error) { + nn := make([]envoy.Node, 0, 16) + nn = append(nn, &envoy.ComposeModuleNode{Module: wrap.res}) + + return nn, nil +} + func (set *ComposeModuleFieldSet) UnmarshalYAML(n *yaml.Node) error { return iterator(n, func(k, v *yaml.Node) (err error) { var ( @@ -138,25 +179,25 @@ func (wrap *ComposeModuleField) UnmarshalYAML(n *yaml.Node) error { return fmt.Errorf("name should be encoded as field definition key") case "label": - return decodeScalar(v, &wrap.res.Label) + return decodeScalar(v, "module field label", &wrap.res.Label) case "kind", "type": - return decodeScalar(v, &wrap.res.Kind) + return decodeScalar(v, "module field kind", &wrap.res.Kind) case "options": return v.Decode(&wrap.res.Options) case "private": - return decodeScalar(v, &wrap.res.Private) + return decodeScalar(v, "module field private", &wrap.res.Private) case "required": - return decodeScalar(v, &wrap.res.Required) + return decodeScalar(v, "module field required", &wrap.res.Required) case "visible": - return decodeScalar(v, &wrap.res.Visible) + return decodeScalar(v, "module field visible", &wrap.res.Visible) case "multi": - return decodeScalar(v, &wrap.res.Multi) + return decodeScalar(v, "module field multi", &wrap.res.Multi) case "default": return fmt.Errorf("field.default /// to be imple,emted") @@ -169,6 +210,10 @@ func (wrap *ComposeModuleField) UnmarshalYAML(n *yaml.Node) error { // // return //}) + + case "allow", "deny": + return decodeAccessRoleOps(wrap.rbacRules, types.ModuleFieldRBACResource, k, v) + } return nil diff --git a/pkg/envoy/yaml/compose_namespace.go b/pkg/envoy/yaml/compose_namespace.go index fe8f4c89a..794adc638 100644 --- a/pkg/envoy/yaml/compose_namespace.go +++ b/pkg/envoy/yaml/compose_namespace.go @@ -2,16 +2,24 @@ package yaml import ( "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/envoy" "github.com/cortezaproject/corteza-server/pkg/handle" "gopkg.in/yaml.v3" ) type ( ComposeNamespace struct { - res *types.Namespace `yaml:",inline"` - ref string - Modules ComposeModuleSet - Rbac `yaml:",inline"` + // when namespace is at least partially defined + res *types.Namespace `yaml:",inline"` + + // when we only have reference (slug) to namespace + ref string + + // all known modules on a namespace + modules ComposeModuleSet + + // module's RBAC rules + *rbacRules } ComposeNamespaceSet []*ComposeNamespace ) @@ -53,7 +61,23 @@ func (wset *ComposeNamespaceSet) UnmarshalYAML(n *yaml.Node) error { }) } +func (wset ComposeNamespaceSet) MarshalEnvoy() ([]envoy.Node, error) { + // namespace usually have bunch of sub-resources defined + nn := make([]envoy.Node, 0, len(wset)*10) + + for _, res := range wset { + if tmp, err := res.MarshalEnvoy(); err != nil { + return nil, err + } else { + nn = append(nn, tmp...) + } + } + + return nn, nil +} + func (wrap *ComposeNamespace) UnmarshalYAML(n *yaml.Node) error { + if isKind(n, yaml.ScalarNode) { wrap.ref = n.Value return nil @@ -73,16 +97,46 @@ func (wrap *ComposeNamespace) UnmarshalYAML(n *yaml.Node) error { return iterator(n, func(k, v *yaml.Node) (err error) { switch k.Value { case "slug": - return decodeScalar(v, &wrap.res.Slug) + return decodeScalar(v, "namespace slug", &wrap.res.Slug) case "name": - return decodeScalar(v, &wrap.res.Name) + return decodeScalar(v, "namespace name", &wrap.res.Name) case "enabled": - return decodeScalar(v, &wrap.res.Enabled) + return decodeScalar(v, "namespace enabled", &wrap.res.Enabled) + + case "modules": + return v.Decode(&wrap.modules) + + case "allow", "deny": + if wrap.rbacRules == nil { + wrap.rbacRules = &rbacRules{} + } + return decodeAccessRoleOps(wrap.rbacRules, types.NamespaceRBACResource, k, v) } return nil }) } + +func (wrap ComposeNamespace) MarshalEnvoy() ([]envoy.Node, error) { + nn := make([]envoy.Node, 0, 1+len(wrap.modules)) + nn = append(nn, &envoy.ComposeNamespaceNode{Ns: wrap.res}) + + if tmp, err := wrap.modules.MarshalEnvoy(); err != nil { + return nil, err + } else { + nn = append(nn, tmp...) + } + + // @todo rbac + + //if tmp, err := wrap.rules.MarshalEnvoy(); err != nil { + // return nil, err + //} else { + // nn = append(nn, tmp...) + //} + + return nn, nil +} diff --git a/pkg/envoy/yaml/compose_namespace_test.go b/pkg/envoy/yaml/compose_namespace_test.go index f5d06dfc7..23cfcb0f8 100644 --- a/pkg/envoy/yaml/compose_namespace_test.go +++ b/pkg/envoy/yaml/compose_namespace_test.go @@ -2,7 +2,8 @@ package yaml import ( "fmt" - "github.com/davecgh/go-spew/spew" + "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/envoy" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" "os" @@ -64,17 +65,17 @@ func TestComposeNamespace_UnmarshalYAML(t *testing.T) { req.Equal("crm", doc.compose.namespaces[0].res.Slug) }) - t.Run("compose namespace file 2", func(t *testing.T) { - doc, err := file(2) - req.NoError(err) - req.NotNil(doc) - spew.Dump(doc.compose) - req.NotNil(doc.compose) - req.Len(doc.compose.namespaces, 1) - req.NotNil(doc.compose.namespaces[0]) +} - req.Equal("CRM", doc.compose.namespaces[0].res.Name) - req.Equal("crm", doc.compose.namespaces[0].res.Slug) - }) +func TestComposeNamespace_MarshalEnvoy(t *testing.T) { + var ( + req = require.New(t) + wrap = ComposeNamespace{res: &types.Namespace{ID: 42}} + nn, err = wrap.MarshalEnvoy() + ) + + req.NoError(err) + req.NotEmpty(nn) + req.Equal(uint64(42), nn[0].(*envoy.ComposeNamespaceNode).Ns.ID) } diff --git a/pkg/envoy/yaml/compose_record.go b/pkg/envoy/yaml/compose_record.go index 8de32d503..eabc100de 100644 --- a/pkg/envoy/yaml/compose_record.go +++ b/pkg/envoy/yaml/compose_record.go @@ -1,7 +1,9 @@ package yaml import ( + "fmt" "github.com/cortezaproject/corteza-server/compose/types" + "github.com/cortezaproject/corteza-server/pkg/envoy" "github.com/cortezaproject/corteza-server/pkg/handle" "gopkg.in/yaml.v3" ) @@ -10,11 +12,12 @@ type ( ComposeRecord struct { res *types.Record `yaml:",inline"` values ComposeRecordValues `yaml:"values"` - moduleRef string - createdByRef string - updatedByRef string - deletedByRef string - ownedByRef string + refModule string + refNamespace string + refCreatedBy string + refUpdatedBy string + refDeletedBy string + refOwnedBy string } ComposeRecordSet []*ComposeRecord @@ -51,7 +54,7 @@ func (wset *ComposeRecordSet) UnmarshalYAML(n *yaml.Node) error { if isKind(v, yaml.SequenceNode) { // multiple records defined return iterator(v, func(_, r *yaml.Node) error { - var wrap = &ComposeRecord{moduleRef: moduleRef} + var wrap = &ComposeRecord{refModule: moduleRef} if err = r.Decode(&wrap); err != nil { return err } @@ -63,7 +66,7 @@ func (wset *ComposeRecordSet) UnmarshalYAML(n *yaml.Node) error { if isKind(v, yaml.MappingNode) { // one record defined - var wrap = &ComposeRecord{moduleRef: moduleRef} + var wrap = &ComposeRecord{refModule: moduleRef} if err = v.Decode(&wrap); err != nil { return } @@ -75,6 +78,29 @@ func (wset *ComposeRecordSet) UnmarshalYAML(n *yaml.Node) error { }) } +func (wset ComposeRecordSet) MarshalEnvoy() ([]envoy.Node, error) { + // namespace usually have bunch of sub-resources defined + var ( + nn = []envoy.Node{} + ) + + // @todo + + return nn, nil +} + +func (set ComposeRecordSet) setNamespaceRef(ref string) error { + for _, res := range set { + if res.refNamespace != "" && ref != res.refNamespace { + return fmt.Errorf("cannot override namespace reference %s with %s", res.refNamespace, ref) + } + + res.refNamespace = ref + } + + return nil +} + func (wrap *ComposeRecord) UnmarshalYAML(n *yaml.Node) error { if !isKind(n, yaml.MappingNode) { return nodeErr(n, "expecting mapping node for record definition") @@ -87,7 +113,7 @@ func (wrap *ComposeRecord) UnmarshalYAML(n *yaml.Node) error { return iterator(n, func(k, v *yaml.Node) error { switch k.Value { case "module": - return decodeRef(v, "module", &wrap.moduleRef) + return decodeRef(v, "module", &wrap.refModule) case "values": // Use aux structure to decode record values into RVS @@ -106,13 +132,13 @@ func (wrap *ComposeRecord) UnmarshalYAML(n *yaml.Node) error { case "deletedAt": return v.Decode(&wrap.res.DeletedAt) case "createdBy": - return decodeRef(v, "user", &wrap.createdByRef) + return decodeRef(v, "createdBy user", &wrap.refCreatedBy) case "updatedBy": - return decodeRef(v, "user", &wrap.updatedByRef) + return decodeRef(v, "updatedBy user", &wrap.refUpdatedBy) case "deletedBy": - return decodeRef(v, "user", &wrap.deletedByRef) + return decodeRef(v, "deletedBy user", &wrap.refDeletedBy) case "ownedBy": - return decodeRef(v, "user", &wrap.ownedByRef) + return decodeRef(v, "ownedBy user", &wrap.refOwnedBy) default: return nodeErr(k, "unsupported key %s used for record definition", k.Value) diff --git a/pkg/envoy/yaml/compose_test.go b/pkg/envoy/yaml/compose_test.go index 1027b44d7..176832be2 100644 --- a/pkg/envoy/yaml/compose_test.go +++ b/pkg/envoy/yaml/compose_test.go @@ -34,13 +34,10 @@ func TestCompose_UnmarshalYAML(t *testing.T) { req.Len(c.namespaces, 1) c, err = parse(&compose{}, `namespace: { }`) - req.NoError(err) - req.NotNil(c) - req.Len(c.namespaces, 1) + req.Error(err) c, err = parse(&compose{}, `namespace: foo`) req.NoError(err) req.NotNil(c) - req.Len(c.namespaces, 1) - req.Equal("foo", c.namespaces[0].ref) + req.Empty(c.namespaces, "namespace ref should not result in namespace definition") } diff --git a/pkg/envoy/yaml/decoder.go b/pkg/envoy/yaml/decoder.go index aa6a8d222..36ed42853 100644 --- a/pkg/envoy/yaml/decoder.go +++ b/pkg/envoy/yaml/decoder.go @@ -3,7 +3,6 @@ package yaml import ( "context" "fmt" - "github.com/cortezaproject/corteza-server/compose/types" "github.com/cortezaproject/corteza-server/pkg/envoy" "gopkg.in/yaml.v3" "io" @@ -94,7 +93,7 @@ func (y *decoder) Decode(ctx context.Context, r io.Reader, i os.FileInfo) ([]env // for modRef, rr := range c.Records { // // We can define a basic module representation as it will be updated later // // during validation/runtime -// mod := &types.Module{} +// mod := &types.module{} // mod.Handle = modRef // mod.Name = modRef // @@ -109,55 +108,55 @@ func (y *decoder) Decode(ctx context.Context, r io.Reader, i os.FileInfo) ([]env // return nn, nil //} -func (y *decoder) convertNamespaces(nss ComposeNamespaceSet) ([]envoy.Node, error) { - nn := make([]envoy.Node, 0, 2) - - for _, ns := range nss { - nn = append(nn, &envoy.ComposeNamespaceNode{Ns: ns.res}) - - // Nested modules - if ns.Modules != nil { - mm, err := y.convertModules(ns.Modules, ns.res) - if err != nil { - return nil, err - } - nn = append(nn, mm...) - } - - // @todo nested RBAC - } - - return nn, nil -} - -func (y *decoder) convertModules(mm ComposeModuleSet, ns *types.Namespace) ([]envoy.Node, error) { - nn := make([]envoy.Node, 0) - - for _, m := range mm { - nn = append(nn, &envoy.ComposeModuleNode{ - Mod: m.res, - Ns: ns, - }) - - // @todo nested resources; should there be any? - } - - return nn, nil -} - -func (y *decoder) convertRecords(rr ComposeRecordSet, m *types.Module) ([]envoy.Node, error) { - // Iterator function for providing records to be imported. - // This doesn't do any validation; that should be handled by other layers. - f := func(f func(record *types.Record) error) error { - //for _, r := range rr { - //err := f(r.res) - //if err != nil { - // return err - //} - //} - - return nil - } - - return []envoy.Node{&envoy.ComposeRecordNode{Mod: m, Walk: f}}, nil -} +//func (y *decoder) convertNamespaces(nss ComposeNamespaceSet) ([]envoy.Node, error) { +// nn := make([]envoy.Node, 0, 2) +// +// for _, ns := range nss { +// nn = append(nn, &envoy.ComposeNamespaceNode{Ns: ns.res}) +// +// // Nested modules +// if ns.modules != nil { +// mm, err := y.convertModules(ns.modules, ns.res) +// if err != nil { +// return nil, err +// } +// nn = append(nn, mm...) +// } +// +// // @todo nested RBAC +// } +// +// return nn, nil +//} +// +//func (y *decoder) convertModules(mm ComposeModuleSet, ns *types.Namespace) ([]envoy.Node, error) { +// nn := make([]envoy.Node, 0) +// +// for _, m := range mm { +// nn = append(nn, &envoy.ComposeModuleNode{ +// module: m.res, +// Ns: ns, +// }) +// +// // @todo nested resources; should there be any? +// } +// +// return nn, nil +//} +// +//func (y *decoder) convertRecords(rr ComposeRecordSet, m *types.Module) ([]envoy.Node, error) { +// // Iterator function for providing records to be imported. +// // This doesn't do any validation; that should be handled by other layers. +// f := func(f func(record *types.Record) error) error { +// //for _, r := range rr { +// //err := f(r.res) +// //if err != nil { +// // return err +// //} +// //} +// +// return nil +// } +// +// return []envoy.Node{&envoy.ComposeRecordNode{Mod: m, Walk: f}}, nil +//} diff --git a/pkg/envoy/yaml/doc.go b/pkg/envoy/yaml/doc.go index c8bbdc165..f21c12830 100644 --- a/pkg/envoy/yaml/doc.go +++ b/pkg/envoy/yaml/doc.go @@ -30,6 +30,16 @@ func (doc *Document) UnmarshalYAML(n *yaml.Node) error { func (doc *Document) Decode(ctx context.Context, l loader) ([]envoy.Node, error) { nn := make([]envoy.Node, 0, 100) + if doc.compose != nil { + if tmp, err := doc.compose.MarshalEnvoy(); err != nil { + return nil, err + } else { + nn = append(nn, tmp...) + } + } + + return nn, nil + //// In case of namespaces... //if doc.Namespaces != nil { // nodes, err := doc.Namespaces.Decode(ctx, l) @@ -67,7 +77,7 @@ func (doc *Document) Decode(ctx context.Context, l loader) ([]envoy.Node, error) // for modRef, rr := range doc.Records { // // We can define a basic module representation as it will be updated later // // during validation/runtime - // mod := &types.Module{} + // mod := &types.module{} // mod.Handle = modRef // mod.Name = modRef // diff --git a/pkg/envoy/yaml/rbac.go b/pkg/envoy/yaml/rbac.go index 81ac3048a..5eb0075ea 100644 --- a/pkg/envoy/yaml/rbac.go +++ b/pkg/envoy/yaml/rbac.go @@ -1,9 +1,53 @@ package yaml +import ( + "github.com/cortezaproject/corteza-server/pkg/rbac" + "gopkg.in/yaml.v3" +) + type ( - RbacRules map[string][]string - Rbac struct { - Allow RbacRules - Deny RbacRules + rbacRules struct { + // mapping unresolved roles to defined RBAC rules + rules map[string]rbac.RuleSet } ) + +// Decodes RBAC rules defined as access/role/ops... +func decodeAccessRoleOps(wrap *rbacRules, res rbac.Resource, k, v *yaml.Node) error { + var ( + access rbac.Access + ) + + if wrap.rules == nil { + wrap.rules = make(map[string]rbac.RuleSet) + } + + if !isKind(v, yaml.MappingNode) { + return nodeErr(v, "rbac rule definition must be a map") + } + + if k.Value == "allow" { + access = rbac.Allow + } + + return iterator(v, func(k, v *yaml.Node) error { + + var ( + role = k.Value + ) + + if _, set := wrap.rules[role]; !set { + wrap.rules[role] = rbac.RuleSet{} + } + + if !isKind(v, yaml.SequenceNode) { + return nodeErr(v, "rbac rule operations list must be a sequence") + } + + return iterator(v, func(_, v *yaml.Node) error { + rule := &rbac.Rule{Resource: res, Operation: rbac.Operation(v.Value), Access: access} + wrap.rules[role] = append(wrap.rules[role], rule) + return nil + }) + }) +} diff --git a/pkg/envoy/yaml/testdata/compose_namespace_2.yaml b/pkg/envoy/yaml/testdata/compose_namespace_2.yaml deleted file mode 100644 index 2158b83d1..000000000 --- a/pkg/envoy/yaml/testdata/compose_namespace_2.yaml +++ /dev/null @@ -1,10 +0,0 @@ -namespace: - name: CRM - slug: crm - allow: - everyone: - - module.create - - read - deny: - everyone: - - delete diff --git a/pkg/envoy/yaml/yaml.go b/pkg/envoy/yaml/yaml.go index 3d038c07f..e03065e50 100644 --- a/pkg/envoy/yaml/yaml.go +++ b/pkg/envoy/yaml/yaml.go @@ -84,9 +84,9 @@ func findKeyNode(n *yaml.Node, key string) *yaml.Node { } // Checks validity of ref node and sets the value to given arg ptr -func decodeScalar(n *yaml.Node, val interface{}) error { +func decodeScalar(n *yaml.Node, name string, val interface{}) error { if !isKind(n, yaml.ScalarNode) { - return nodeErr(n, "expecting scalar value") + return nodeErr(n, "expecting scalar value for %s", name) } return n.Decode(val)