3
0

Add page layout definitons to envoy

This commit is contained in:
Tomaž Jerman
2023-04-02 14:26:05 +02:00
committed by Jože Fortun
parent 7bf2234daf
commit 9ec7139c84
14 changed files with 1032 additions and 19 deletions

View File

@@ -10,9 +10,9 @@ import (
[
for cmp in app.corteza.components {
template: "gocode/envoy/rbac_references_$component.go.tpl"
output: "pkg/envoy/resource/rbac_references_\(cmp.ident).gen.go"
output: "pkg/envoyx/rbac_references_\(cmp.ident).gen.go"
payload: {
package: "resource"
package: "envoyx"
imports: [
"\"github.com/cortezaproject/corteza/server/\(cmp.ident)/types\"",
]

View File

@@ -128,6 +128,17 @@ func (d StoreDecoder) decode(ctx context.Context, s store.Storer, dl dal.FullSer
}
out = append(out, aux...)
case types.PageLayoutResourceType:
aux, err = d.decodePageLayout(ctx, s, dl, d.makePageLayoutFilter(scopedNodes[i], refNodes[i], wf.f))
if err != nil {
return
}
for _, a := range aux {
a.Identifiers = a.Identifiers.Merge(wf.f.Identifiers)
a.References = envoyx.MergeRefs(a.References, refRefs[i])
}
out = append(out, aux...)
default:
aux, err = d.extendDecoder(ctx, s, dl, wf.rt, refNodes[i], wf.f)
if err != nil {
@@ -629,6 +640,132 @@ func (d StoreDecoder) makePageFilter(scope *envoyx.Node, refs map[string]*envoyx
return
}
// // // // // // // // // // // // // // // // // // // // // // // // //
// Functions for resource pageLayout
// // // // // // // // // // // // // // // // // // // // // // // // //
func (d StoreDecoder) decodePageLayout(ctx context.Context, s store.Storer, dl dal.FullService, f types.PageLayoutFilter) (out envoyx.NodeSet, err error) {
// @todo this might need to be improved.
// Currently, no resource is vast enough to pose a problem.
rr, _, err := store.SearchComposePageLayouts(ctx, s, f)
if err != nil {
return
}
for _, r := range rr {
var n *envoyx.Node
n, err = PageLayoutToEnvoyNode(r)
if err != nil {
return
}
out = append(out, n)
}
return
}
func PageLayoutToEnvoyNode(r *types.PageLayout) (node *envoyx.Node, err error) {
// Identifiers
ii := envoyx.MakeIdentifiers(
r.Handle,
r.ID,
)
// Handle references
// Omit any non-defined values
refs := map[string]envoyx.Ref{}
if r.NamespaceID > 0 {
refs["NamespaceID"] = envoyx.Ref{
ResourceType: "corteza::compose:namespace",
Identifiers: envoyx.MakeIdentifiers(r.NamespaceID),
}
}
if r.OwnedBy > 0 {
refs["OwnedBy"] = envoyx.Ref{
ResourceType: "corteza::system:user",
Identifiers: envoyx.MakeIdentifiers(r.OwnedBy),
}
}
if r.PageID > 0 {
refs["PageID"] = envoyx.Ref{
ResourceType: "corteza::compose:page",
Identifiers: envoyx.MakeIdentifiers(r.PageID),
}
}
if r.ParentID > 0 {
refs["ParentID"] = envoyx.Ref{
ResourceType: "corteza::compose:page-layout",
Identifiers: envoyx.MakeIdentifiers(r.ParentID),
}
}
var scope envoyx.Scope
scope = envoyx.Scope{
ResourceType: refs["NamespaceID"].ResourceType,
Identifiers: refs["NamespaceID"].Identifiers,
}
for k, ref := range refs {
// @todo temporary solution to not needlessly scope resources.
// Optimally, this would be selectively handled by codegen.
if !strings.HasPrefix(ref.ResourceType, "corteza::compose") {
continue
}
ref.Scope = scope
refs[k] = ref
}
node = &envoyx.Node{
Resource: r,
ResourceType: types.PageLayoutResourceType,
Identifiers: ii,
References: refs,
Scope: scope,
}
return
}
func (d StoreDecoder) makePageLayoutFilter(scope *envoyx.Node, refs map[string]*envoyx.Node, auxf envoyx.ResourceFilter) (out types.PageLayoutFilter) {
out.Limit = auxf.Limit
ids, hh := auxf.Identifiers.Idents()
_ = ids
_ = hh
out.PageLayoutID = ids
if len(hh) > 0 {
out.Handle = hh[0]
}
// Refs
var (
ar *envoyx.Node
ok bool
)
_ = ar
_ = ok
ar, ok = refs["NamespaceID"]
if ok {
out.NamespaceID = ar.Resource.GetID()
}
ar, ok = refs["PageID"]
if ok {
out.PageID = ar.Resource.GetID()
}
ar, ok = refs["ParentID"]
if ok {
out.ParentID = ar.Resource.GetID()
}
return
}
// // // // // // // // // // // // // // // // // // // // // // // // //
// Utilities
// // // // // // // // // // // // // // // // // // // // // // // // //

View File

@@ -55,6 +55,8 @@ func (e StoreEncoder) Prepare(ctx context.Context, p envoyx.EncodeParams, rt str
return e.prepareNamespace(ctx, p, s, nn)
case types.PageResourceType:
return e.preparePage(ctx, p, s, nn)
case types.PageLayoutResourceType:
return e.preparePageLayout(ctx, p, s, nn)
default:
return e.prepare(ctx, p, s, rt, nn)
@@ -99,6 +101,9 @@ func (e StoreEncoder) Encode(ctx context.Context, p envoyx.EncodeParams, rt stri
case types.PageResourceType:
return e.encodePages(ctx, p, s, nodes, tree)
case types.PageLayoutResourceType:
return e.encodePageLayouts(ctx, p, s, nodes, tree)
default:
return e.encode(ctx, p, s, rt, nodes, tree)
}
@@ -1171,6 +1176,12 @@ func (e StoreEncoder) encodePage(ctx context.Context, p envoyx.EncodeParams, s s
switch rt {
case types.PageLayoutResourceType:
err = e.encodePageLayouts(ctx, p, s, nn, tree)
if err != nil {
return
}
}
}
@@ -1233,6 +1244,224 @@ func (e StoreEncoder) matchupPages(ctx context.Context, s store.Storer, uu map[i
return
}
// // // // // // // // // // // // // // // // // // // // // // // // //
// Functions for resource pageLayout
// // // // // // // // // // // // // // // // // // // // // // // // //
// preparePageLayout prepares the resources of the given type for encoding
func (e StoreEncoder) preparePageLayout(ctx context.Context, p envoyx.EncodeParams, s store.Storer, nn envoyx.NodeSet) (err error) {
// Grab an index of already existing resources of this type
// @note since these resources should be fairly low-volume and existing for
// a short time (and because we batch by resource type); fetching them all
// into memory shouldn't hurt too much.
// @todo do some benchmarks and potentially implement some smarter check such as
// a bloom filter or something similar.
// Get node scopes
scopedNodes, err := e.getScopeNodes(ctx, s, nn)
if err != nil {
err = errors.Wrap(err, "failed to get scope nodes")
return
}
// Initializing the index here (and using a hashmap) so it's not escaped to the heap
existing := make(map[int]types.PageLayout, len(nn))
err = e.matchupPageLayouts(ctx, s, existing, scopedNodes, nn)
if err != nil {
err = errors.Wrap(err, "failed to matchup existing PageLayouts")
return
}
for i, n := range nn {
if n.Resource == nil {
panic("unexpected state: cannot call preparePageLayout with nodes without a defined Resource")
}
res, ok := n.Resource.(*types.PageLayout)
if !ok {
panic("unexpected resource type: node expecting type of pageLayout")
}
existing, hasExisting := existing[i]
// Run expressions on the nodes
err = e.runEvals(ctx, hasExisting, n)
if err != nil {
return
}
if hasExisting {
// On existing, we don't need to re-do identifiers and references; simply
// changing up the internal resource is enough.
//
// In the future, we can pass down the tree and re-do the deps like that
switch n.Config.MergeAlg {
case envoyx.OnConflictPanic:
err = errors.Errorf("resource %v already exists", n.Identifiers.Slice)
return
case envoyx.OnConflictReplace:
// Replace; simple ID change should do the trick
res.ID = existing.ID
case envoyx.OnConflictSkip:
// Replace the node's resource with the fetched one
res = &existing
// @todo merging
}
} else {
// @todo actually a bottleneck. As per sonyflake docs, it can at most
// generate up to 2**8 (256) IDs per 10ms in a single thread.
// How can we improve this?
res.ID = id.Next()
}
// We can skip validation/defaults when the resource is overwritten by
// the one already stored (the panic one errors out anyway) since it
// should already be ok.
if !hasExisting || n.Config.MergeAlg != envoyx.OnConflictSkip {
err = e.setPageLayoutDefaults(res)
if err != nil {
return err
}
err = e.validatePageLayout(res)
if err != nil {
return err
}
}
n.Resource = res
}
return
}
// encodePageLayouts encodes a set of resource into the database
func (e StoreEncoder) encodePageLayouts(ctx context.Context, p envoyx.EncodeParams, s store.Storer, nn envoyx.NodeSet, tree envoyx.Traverser) (err error) {
for _, n := range nn {
err = e.encodePageLayout(ctx, p, s, n, tree)
if err != nil {
return
}
}
return
}
// encodePageLayout encodes the resource into the database
func (e StoreEncoder) encodePageLayout(ctx context.Context, p envoyx.EncodeParams, s store.Storer, n *envoyx.Node, tree envoyx.Traverser) (err error) {
// Grab dependency references
var auxID uint64
err = func() (err error) {
for fieldLabel, ref := range n.References {
rn := tree.ParentForRef(n, ref)
if rn == nil {
err = fmt.Errorf("parent reference %v not found", ref)
return
}
auxID = rn.Resource.GetID()
if auxID == 0 {
err = fmt.Errorf("parent reference does not provide an identifier")
return
}
err = n.Resource.SetValue(fieldLabel, 0, auxID)
if err != nil {
return
}
}
return
}()
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("failed to set dependency references for %s %v", n.ResourceType, n.Identifiers.Slice))
return
}
// Flush to the DB
if !n.Evaluated.Skip {
err = store.UpsertComposePageLayout(ctx, s, n.Resource.(*types.PageLayout))
if err != nil {
err = errors.Wrap(err, "failed to upsert PageLayout")
return
}
}
// Handle resources nested under it
//
// @todo how can we remove the OmitPlaceholderNodes call the same way we did for
// the root function calls?
err = func() (err error) {
for rt, nn := range envoyx.NodesByResourceType(tree.Children(n)...) {
nn = envoyx.OmitPlaceholderNodes(nn...)
switch rt {
}
}
return
}()
if err != nil {
err = errors.Wrap(err, "failed to encode nested resources")
return
}
return
}
// matchupPageLayouts returns an index with indicates what resources already exist
func (e StoreEncoder) matchupPageLayouts(ctx context.Context, s store.Storer, uu map[int]types.PageLayout, scopes envoyx.NodeSet, nn envoyx.NodeSet) (err error) {
// @todo might need to do it smarter then this.
// Most resources won't really be that vast so this should be acceptable for now.
aa, _, err := store.SearchComposePageLayouts(ctx, s, types.PageLayoutFilter{})
if err != nil {
return
}
idMap := make(map[uint64]*types.PageLayout, len(aa))
strMap := make(map[string]*types.PageLayout, len(aa))
for _, a := range aa {
strMap[a.Handle] = a
idMap[a.ID] = a
}
var aux *types.PageLayout
var ok bool
for i, n := range nn {
scope := scopes[i]
if scope == nil {
continue
}
for _, idf := range n.Identifiers.Slice {
if id, err := strconv.ParseUint(idf, 10, 64); err == nil {
aux, ok = idMap[id]
if ok {
uu[i] = *aux
// When any identifier matches we can end it
break
}
}
aux, ok = strMap[idf]
if ok {
uu[i] = *aux
// When any identifier matches we can end it
break
}
}
}
return
}
// // // // // // // // // // // // // // // // // // // // // // // // //
// Utility functions
// // // // // // // // // // // // // // // // // // // // // // // // //

View File

@@ -117,6 +117,21 @@ func (e StoreEncoder) validatePage(*types.Page) (err error) {
return
}
func (e StoreEncoder) setPageLayoutDefaults(res *types.PageLayout) (err error) {
if res.CreatedAt.IsZero() {
res.CreatedAt = time.Now()
}
// @note these pageblocks reference the ones define on the page so nothing
// to do here.
return
}
func (e StoreEncoder) validatePageLayout(*types.PageLayout) (err error) {
return
}
func (e StoreEncoder) setRecordDefaults(*types.Record) (err error) {
return
}

View File

@@ -181,6 +181,21 @@ func (d *auxYamlDoc) UnmarshalYAML(n *yaml.Node) (err error) {
}
return err
case "pagelayout", "page_layouts", "pagelayouts", "layouts":
if y7s.IsMapping(v) {
aux, err = d.unmarshalPageLayoutMap(dctx, v)
d.nodes = append(d.nodes, aux...)
}
if y7s.IsSeq(v) {
aux, err = d.unmarshalPageLayoutSeq(dctx, v)
d.nodes = append(d.nodes, aux...)
}
if err != nil {
err = errors.Wrap(err, "failed to unmarshal node: pageLayout")
}
return err
// Offload to custom handlers
default:
aux, err = d.unmarshalYAML(kv, v)
@@ -1432,6 +1447,20 @@ func (d *auxYamlDoc) unmarshalNamespaceNode(dctx documentContext, n *yaml.Node,
}
break
case "pagelayout", "page_layouts", "pagelayouts", "layouts":
if y7s.IsSeq(n) {
nestedNodes, err = d.unmarshalPageLayoutSeq(dctx, n)
if err != nil {
return err
}
} else {
nestedNodes, err = d.unmarshalPageLayoutMap(dctx, n)
if err != nil {
return err
}
}
break
}
// Iterate nested nodes and update their reference to the current resource
@@ -1832,6 +1861,20 @@ func (d *auxYamlDoc) unmarshalPageNode(dctx documentContext, n *yaml.Node, meta
switch strings.ToLower(k.Value) {
case "pagelayout", "page_layouts", "pagelayouts", "layouts":
if y7s.IsSeq(n) {
nestedNodes, err = d.unmarshalPageLayoutSeq(dctx, n)
if err != nil {
return err
}
} else {
nestedNodes, err = d.unmarshalPageLayoutMap(dctx, n)
if err != nil {
return err
}
}
break
case "children", "pages":
if y7s.IsSeq(n) {
nestedNodes, err = d.unmarshalExtendedPagesSeq(dctx, n)
@@ -1950,6 +1993,360 @@ func (d *auxYamlDoc) unmarshalPageNode(dctx documentContext, n *yaml.Node, meta
return
}
// // // // // // // // // // // // // // // // // // // // // // // // //
// Functions for resource pageLayout
// // // // // // // // // // // // // // // // // // // // // // // // //
// unmarshalPageLayoutSeq unmarshals PageLayout when provided as a sequence node
func (d *auxYamlDoc) unmarshalPageLayoutSeq(dctx documentContext, n *yaml.Node) (out envoyx.NodeSet, err error) {
var aux envoyx.NodeSet
err = y7s.EachSeq(n, func(n *yaml.Node) error {
aux, err = d.unmarshalPageLayoutNode(dctx, n)
if err != nil {
return err
}
out = append(out, aux...)
return nil
})
return
}
// unmarshalPageLayoutMap unmarshals PageLayout when provided as a mapping node
//
// When map encoded, the map key is used as a preset identifier.
// The identifier is passed to the node function as a meta node
func (d *auxYamlDoc) unmarshalPageLayoutMap(dctx documentContext, n *yaml.Node) (out envoyx.NodeSet, err error) {
var aux envoyx.NodeSet
err = y7s.EachMap(n, func(k, n *yaml.Node) error {
aux, err = d.unmarshalPageLayoutNode(dctx, n, k)
if err != nil {
return err
}
out = append(out, aux...)
return nil
})
return
}
// unmarshalPageLayoutNode is a cookie-cutter function to unmarshal
// the yaml node into the corresponding Corteza type & Node
func (d *auxYamlDoc) unmarshalPageLayoutNode(dctx documentContext, n *yaml.Node, meta ...*yaml.Node) (out envoyx.NodeSet, err error) {
var r *types.PageLayout
// @todo we're omitting errors because there will be a bunch due to invalid
// resource field types. This might be a bit unstable as other errors may
// also get ignored.
//
// A potential fix would be to firstly unmarshal into an any, check errors
// and then unmarshal into the resource while omitting errors.
n.Decode(&r)
// Identifiers are determined manually when iterating the yaml node.
// This is to help assure there are no duplicates and everything
// was accounted for especially when working with aliases such as
// user_name instead of userName.
ii := envoyx.Identifiers{}
// When a resource supports mapped input, the key is passed as meta which
// needs to be registered as an identifier (since it is)
if len(meta) > 0 {
y7s.DecodeScalar(meta[0], "Handle", &r.Handle)
ii = ii.Add(r.Handle)
}
var (
refs = make(map[string]envoyx.Ref)
auxOut envoyx.NodeSet
nestedNodes envoyx.NodeSet
scope envoyx.Scope
envoyConfig envoyx.EnvoyConfig
rbacNodes envoyx.NodeSet
)
_ = auxOut
_ = refs
err = y7s.EachMap(n, func(k, n *yaml.Node) error {
var auxNodeValue any
_ = auxNodeValue
switch strings.ToLower(k.Value) {
case "handle":
// Handle identifiers
err = y7s.DecodeScalar(n, "handle", &auxNodeValue)
if err != nil {
return err
}
ii = ii.Add(auxNodeValue)
break
case "id":
// Handle identifiers
err = y7s.DecodeScalar(n, "id", &auxNodeValue)
if err != nil {
return err
}
ii = ii.Add(auxNodeValue)
break
case "namespaceid", "namespace":
// Handle references
err = y7s.DecodeScalar(n, "namespaceID", &auxNodeValue)
if err != nil {
return err
}
// Omit if not defined
tmp := cast.ToString(auxNodeValue)
if tmp == "0" || tmp == "" {
break
}
refs["NamespaceID"] = envoyx.Ref{
ResourceType: "corteza::compose:namespace",
Identifiers: envoyx.MakeIdentifiers(auxNodeValue),
}
break
case "ownedby":
// Handle references
err = y7s.DecodeScalar(n, "ownedBy", &auxNodeValue)
if err != nil {
return err
}
// Omit if not defined
tmp := cast.ToString(auxNodeValue)
if tmp == "0" || tmp == "" {
break
}
refs["OwnedBy"] = envoyx.Ref{
ResourceType: "corteza::system:user",
Identifiers: envoyx.MakeIdentifiers(auxNodeValue),
}
break
case "pageid", "page":
// Handle references
err = y7s.DecodeScalar(n, "pageID", &auxNodeValue)
if err != nil {
return err
}
// Omit if not defined
tmp := cast.ToString(auxNodeValue)
if tmp == "0" || tmp == "" {
break
}
refs["PageID"] = envoyx.Ref{
ResourceType: "corteza::compose:page",
Identifiers: envoyx.MakeIdentifiers(auxNodeValue),
}
break
case "parentid", "parent":
// Handle references
err = y7s.DecodeScalar(n, "parentID", &auxNodeValue)
if err != nil {
return err
}
// Omit if not defined
tmp := cast.ToString(auxNodeValue)
if tmp == "0" || tmp == "" {
break
}
refs["ParentID"] = envoyx.Ref{
ResourceType: "corteza::compose:page-layout",
Identifiers: envoyx.MakeIdentifiers(auxNodeValue),
}
break
// Handle RBAC rules
case "allow":
auxOut, err = unmarshalAllowNode(n)
if err != nil {
return err
}
rbacNodes = append(rbacNodes, auxOut...)
auxOut = nil
case "deny":
auxOut, err = unmarshalDenyNode(n)
if err != nil {
return err
}
rbacNodes = append(rbacNodes, auxOut...)
auxOut = nil
case "(envoy)":
envoyConfig = d.decodeEnvoyConfig(n)
}
return nil
})
if err != nil {
return
}
// Make parent identifiers available through the dctx
dctx.parentIdent = ii
// Handle global namespace reference which can be provided as the doc. context
//
// @todo this is a temporary solution and should be extended when the document
// context needs to be extended.
// Limit this only to the compose resource since that is the only scenario
// the previous implementation supports.
if ref, ok := dctx.references["namespace"]; ok {
refs["NamespaceID"] = envoyx.Ref{
ResourceType: types.NamespaceResourceType,
Identifiers: envoyx.MakeIdentifiers(ref),
}
}
// Define the scope
//
// This resource is scoped to the first parent (generally the namespace)
// when talking about Compose resources (the only supported scenario at the moment).
scope = envoyx.Scope{
ResourceType: refs["NamespaceID"].ResourceType,
Identifiers: refs["NamespaceID"].Identifiers,
}
// Apply the scope to all of the references of the same type
for k, ref := range refs {
if !strings.HasPrefix(ref.ResourceType, "corteza::compose") {
continue
}
ref.Scope = scope
refs[k] = ref
}
// Handle any resources that could be inserted under pageLayout such as a module inside a namespace
//
// This operation is done in the second pass of the document so we have
// the complete context of the current resource; such as the identifier,
// references, and scope.
var auxNestedNodes envoyx.NodeSet
err = y7s.EachMap(n, func(k, n *yaml.Node) error {
nestedNodes = nil
switch strings.ToLower(k.Value) {
}
// Iterate nested nodes and update their reference to the current resource
//
// Any reference to the parent resource from the child resource is overwritten
// to avoid potential user-error edge cases.
for _, a := range nestedNodes {
// @note all nested resources fall under the same component and the same scope.
// Simply assign the same scope to all -- if it shouldn't be scoped
// the parent won't have it (saving CPU ticks :)
a.Scope = scope
if a.References == nil {
a.References = make(map[string]envoyx.Ref)
}
a.References["PageLayoutID"] = envoyx.Ref{
ResourceType: types.PageLayoutResourceType,
Identifiers: ii,
Scope: scope,
}
for f, ref := range a.References {
if !strings.HasPrefix(ref.ResourceType, "corteza::compose") {
continue
}
ref.Scope = scope
a.References[f] = ref
}
for f, ref := range refs {
// Only inherit root references
// @todo improve; this is a hack
if strings.Contains(f, ".") {
continue
}
// Only assume refs if they're not yet set
if _, ok := a.References[f]; ok {
continue
}
a.References[f] = ref
}
}
auxNestedNodes = append(auxNestedNodes, nestedNodes...)
return nil
})
if err != nil {
return
}
out = append(out, auxNestedNodes...)
a := &envoyx.Node{
Resource: r,
ResourceType: types.PageLayoutResourceType,
Identifiers: ii,
References: refs,
Scope: scope,
Config: envoyConfig,
}
// Update RBAC resource nodes with references regarding the resource
rres := r.RbacResource()
for _, rn := range rbacNodes {
aux := rn.Resource.(*rbac.Rule)
if aux.Resource == "" {
aux.Resource = rres
}
// Since the rule belongs to the resource, it will have the same
// subset of references as the parent resource.
rn.References = envoyx.MergeRefs(rn.References, a.References)
// The RBAC rule's most specific identifier is the resource itself.
// Using this we can hardcode it to point to the location after the parent resource.
//
// @todo consider using a more descriptive identifier for the position
// such as `index-%d`.
rn.References["2"] = envoyx.Ref{
ResourceType: a.ResourceType,
Identifiers: a.Identifiers,
Scope: scope,
}
for _, r := range rn.References {
if r.Scope.IsEmpty() {
continue
}
rn.Scope = r.Scope
break
}
}
// Put it all together...
out = append(out, a)
out = append(out, auxOut...)
out = append(out, rbacNodes...)
return
}
// // // // // // // // // // // // // // // // // // // // // // // // //
// RBAC unmarshal logic
// // // // // // // // // // // // // // // // // // // // // // // // //

View File

@@ -105,6 +105,16 @@ func (e YamlEncoder) Encode(ctx context.Context, p envoyx.EncodeParams, rt strin
if err != nil {
return
}
case types.PageLayoutResourceType:
aux, err = e.encodePageLayouts(ctx, p, nodes, tt)
if err != nil {
return
}
// Root level resources are always encoded as a map
out, err = y7s.AddMap(out, "pageLayout", aux)
if err != nil {
return
}
default:
out, err = e.encode(ctx, out, p, rt, nodes, tt)
@@ -540,6 +550,102 @@ func (e YamlEncoder) encodePage(ctx context.Context, p envoyx.EncodeParams, node
var aux *yaml.Node
_ = aux
aux, err = e.encodePageLayouts(ctx, p, tt.ChildrenForResourceType(node, types.PageLayoutResourceType), tt)
if err != nil {
return
}
out, err = y7s.AddMap(out,
"pageLayout", aux,
)
if err != nil {
return
}
return
}
// // // // // // // // // // // // // // // // // // // // // // // // //
// Functions for resource pageLayout
// // // // // // // // // // // // // // // // // // // // // // // // //
func (e YamlEncoder) encodePageLayouts(ctx context.Context, p envoyx.EncodeParams, nodes envoyx.NodeSet, tt envoyx.Traverser) (out *yaml.Node, err error) {
var aux *yaml.Node
for _, n := range nodes {
aux, err = e.encodePageLayout(ctx, p, n, tt)
if err != nil {
return
}
out, err = y7s.AddSeq(out, aux)
if err != nil {
return
}
}
return
}
// encodePageLayout focuses on the specific resource invoked by the Encode method
func (e YamlEncoder) encodePageLayout(ctx context.Context, p envoyx.EncodeParams, node *envoyx.Node, tt envoyx.Traverser) (out *yaml.Node, err error) {
res := node.Resource.(*types.PageLayout)
// Pre-compute some map values so we can omit error checking when encoding yaml nodes
auxCreatedAt, err := e.encodeTimestamp(p, res.CreatedAt)
if err != nil {
return
}
auxDeletedAt, err := e.encodeTimestampNil(p, res.DeletedAt)
if err != nil {
return
}
auxNamespaceID, err := e.encodeRef(p, res.NamespaceID, "NamespaceID", node, tt)
if err != nil {
return
}
auxOwnedBy, err := e.encodeRef(p, res.OwnedBy, "OwnedBy", node, tt)
if err != nil {
return
}
auxPageID, err := e.encodeRef(p, res.PageID, "PageID", node, tt)
if err != nil {
return
}
auxParentID, err := e.encodeRef(p, res.ParentID, "ParentID", node, tt)
if err != nil {
return
}
auxUpdatedAt, err := e.encodeTimestampNil(p, res.UpdatedAt)
if err != nil {
return
}
out, err = y7s.AddMap(out,
"blocks", res.Blocks,
"config", res.Config,
"createdAt", auxCreatedAt,
"deletedAt", auxDeletedAt,
"handle", res.Handle,
"id", res.ID,
"meta", res.Meta,
"namespaceID", auxNamespaceID,
"ownedBy", auxOwnedBy,
"pageID", auxPageID,
"parentID", auxParentID,
"primary", res.Primary,
"updatedAt", auxUpdatedAt,
"weight", res.Weight,
)
if err != nil {
return
}
// Handle nested resources
var aux *yaml.Node
_ = aux
return
}

View File

@@ -20,12 +20,22 @@ pageLayout: {
goType: "uint64",
dal: { type: "Ref", refModelResType: "corteza::compose:page" }
sortable: true
envoy: {
yaml: {
identKeyAlias: ["page"]
}
}
}
parent_id: {
ident: "parentID",
goType: "uint64",
dal: { type: "Ref", refModelResType: "corteza::compose:page-layout" }
sortable: true
envoy: {
yaml: {
identKeyAlias: ["parent"]
}
}
}
namespace_id: {
@@ -33,6 +43,11 @@ pageLayout: {
goType: "uint64",
storeIdent: "rel_namespace"
dal: { type: "Ref", refModelResType: "corteza::compose:namespace" }
envoy: {
yaml: {
identKeyAlias: ["namespace"]
}
}
}
weight: {
goType: "int", sortable: true
@@ -84,26 +99,28 @@ pageLayout: {
filter: {
struct: {
page_layout_id: { goType: "[]uint64", ident: "pageLayoutID", storeIdent: "id" }
namespace_id: { goType: "uint64", ident: "namespaceID", storeIdent: "rel_namespace" }
page_id: { goType: "uint64", ident: "pageID", storeIdent: "page_id" }
parent_id: { goType: "uint64", ident: "parentID", storeIdent: "parent_id" }
default: { goType: "bool", ident: "default" }
handle: { goType: "string" }
deleted: { goType: "filter.State", storeIdent: "deleted_at" }
}
query: ["handle"]
byValue: ["handle", "namespace_id", "page_id"]
byValue: ["handle", "parent_id", "namespace_id", "page_id", "page_layout_id"]
byNilState: ["deleted"]
}
envoy: {
omit: true
}
rbac: {
operations: {
// @todo not sure how RBAC should work here
// we'll probably use the page's RBAC whith some bool flags for user-defined ones
scoped: true
yaml: {
supportMappedInput: true
mappedField: "Handle"
identKeyAlias: ["page_layouts", "pagelayouts", "layouts"]
}
store: {
}
}

View File

@@ -90,6 +90,25 @@ func ComposePageRbacReferences(namespaceID string, page string) (res *Ref, pp []
return
}
// ComposePageLayoutRbacReferences generates RBAC references
//
// Resources with "envoy: false" are skipped
//
// This function is auto-generated
func ComposePageLayoutRbacReferences(namespaceID string, pageID string, pageLayout string) (res *Ref, pp []*Ref, err error) {
if namespaceID != "*" {
pp = append(pp, &Ref{ResourceType: types.NamespaceResourceType, Identifiers: MakeIdentifiers(namespaceID)})
}
if pageID != "*" {
pp = append(pp, &Ref{ResourceType: types.PageResourceType, Identifiers: MakeIdentifiers(pageID)})
}
if pageLayout != "*" {
res = &Ref{ResourceType: types.PageLayoutResourceType, Identifiers: MakeIdentifiers(pageLayout)}
}
return
}
// ComposeRecordRbacReferences generates RBAC references
//
// Resources with "envoy: false" are skipped

View File

@@ -193,6 +193,17 @@ func ParseRule(res string) (string, *Ref, []*Ref, error) {
)
return resourceType, ref, pp, err
case composeTypes.PageLayoutResourceType:
if len(path) != 3 {
return "", nil, nil, fmt.Errorf("expecting 3 reference components in path, got %d", len(path))
}
ref, pp, err := ComposePageLayoutRbacReferences(
path[0],
path[1],
path[2],
)
return resourceType, ref, pp, err
case composeTypes.RecordResourceType:
if len(path) != 3 {
return "", nil, nil, fmt.Errorf("expecting 3 reference components in path, got %d", len(path))

View File

@@ -489,6 +489,45 @@ func SplitResourceIdentifier(ref string) (out map[string]Ref) {
Scope: scope,
}
case "corteza::compose:page-layout":
scope := Scope{}
if gRef(pp, 0) == "" {
return
}
aux := gRef(pp, 0)
if aux != "" {
scope.ResourceType = "corteza::compose:namespace"
scope.Identifiers = MakeIdentifiers(aux)
}
out["Path.0"] = Ref{
ResourceType: "corteza::compose:namespace",
Identifiers: MakeIdentifiers(gRef(pp, 0)),
Scope: scope,
}
if gRef(pp, 1) == "" {
return
}
out["Path.1"] = Ref{
ResourceType: "corteza::compose:page",
Identifiers: MakeIdentifiers(gRef(pp, 1)),
Scope: scope,
}
if gRef(pp, 2) == "" {
return
}
out["Path.2"] = Ref{
ResourceType: "corteza::compose:page-layout",
Identifiers: MakeIdentifiers(gRef(pp, 2)),
Scope: scope,
}
case "corteza::compose:record":
scope := Scope{}

View File

@@ -19,8 +19,9 @@ var (
"corteza::compose:module": true,
"corteza::compose:module-field": true,
"corteza::compose:page": true,
"corteza::compose:record": true,
"corteza::compose:page": true,
"corteza::compose:page-layout": true,
"corteza::compose:record": true,
"corteza::compose:record-datasource": true,
}

View File

@@ -702,6 +702,10 @@ func ComposePageLayoutFilter(d drivers.Dialect, f composeType.PageLayoutFilter)
ee = append(ee, goqu.C("handle").Eq(f.Handle))
}
if f.ParentID > 0 {
ee = append(ee, goqu.C("parent_id").Eq(f.ParentID))
}
if f.NamespaceID > 0 {
ee = append(ee, goqu.C("rel_namespace").Eq(f.NamespaceID))
}
@@ -710,6 +714,10 @@ func ComposePageLayoutFilter(d drivers.Dialect, f composeType.PageLayoutFilter)
ee = append(ee, goqu.C("page_id").Eq(f.PageID))
}
if len(f.PageLayoutID) > 0 {
ee = append(ee, goqu.C("id").In(f.PageLayoutID))
}
if len(f.LabeledIDs) > 0 {
ee = append(ee, goqu.I("id").In(f.LabeledIDs))
}

View File

@@ -75,7 +75,7 @@ func TestImportExport(t *testing.T) {
req.NoError(err)
})
assertState(ctx, t, defaultStore, req)
assertFullState(ctx, t, defaultStore, req)
})
// Prepare a temp file where we'll dump the YAML into
@@ -94,10 +94,11 @@ func TestImportExport(t *testing.T) {
"dal": defaultDal,
},
Filter: map[string]envoyx.ResourceFilter{
types.ChartResourceType: {},
types.ModuleResourceType: {},
types.NamespaceResourceType: {},
types.PageResourceType: {},
types.ChartResourceType: {},
types.ModuleResourceType: {},
types.NamespaceResourceType: {},
types.PageResourceType: {},
types.PageLayoutResourceType: {},
systemTypes.ApplicationResourceType: {},
systemTypes.ApigwRouteResourceType: {},
@@ -173,11 +174,11 @@ func TestImportExport(t *testing.T) {
req.NoError(err)
})
assertState(ctx, t, defaultStore, req)
assertFullState(ctx, t, defaultStore, req)
})
}
func assertState(ctx context.Context, t *testing.T, s store.Storer, req *require.Assertions) {
func assertFullState(ctx context.Context, t *testing.T, s store.Storer, req *require.Assertions) {
t.Run("check state", func(t *testing.T) {
t.Run("corteza::compose", func(t *testing.T) {
// Namespaces
@@ -241,6 +242,25 @@ func assertState(ctx context.Context, t *testing.T, s store.Storer, req *require
rpg1 := pages.FindByHandle("test_ns_1_record_page_1")
req.NotNil(rpg1)
req.Equal(mod1.ID, rpg1.ModuleID)
// Page layouts
layouts, _, err := store.SearchComposePageLayouts(ctx, s, types.PageLayoutFilter{
NamespaceID: ns.ID,
})
req.NoError(err)
ly1 := layouts.FindByHandle("test_ns_1_page_1_layout_1")
req.NotNil(ly1)
ly2 := layouts.FindByHandle("test_ns_1_page_1_layout_2")
req.NotNil(ly2)
// Record page layouts
ly3 := layouts.FindByHandle("test_ns_1_record_page_1_layout_1")
req.NotNil(ly3)
ly4 := layouts.FindByHandle("test_ns_1_record_page_1_layout_2")
req.NotNil(ly4)
})
t.Run("corteza::system", func(t *testing.T) {
// Users

View File

@@ -41,6 +41,13 @@ namespace:
test_ns_1_page_1:
title: Test Namespace 1 Page 1
visible: true
page_layouts:
test_ns_1_page_1_layout_1:
meta:
title: Test Namespace 1 Page 1 Layout 1
test_ns_1_page_1_layout_2:
meta:
title: Test Namespace 1 Page 1 Layout 2
blocks:
- title: Test Namespace 1 Page 1 Block 1
kind: RecordList
@@ -87,6 +94,13 @@ namespace:
test_ns_1_record_page_1:
title: Test Namespace 1 Record Page 1
module: test_ns_1_mod_1
page_layouts:
test_ns_1_record_page_1_layout_1:
meta:
title: Test Namespace 1 Record Page 1 Layout 1
test_ns_1_record_page_1_layout_2:
meta:
title: Test Namespace 1 Record Page 1 Layout 2
application:
test_app_1: