Files
upstream-corteza/server/compose/envoy/store_encode.gen.go
upstream-mirror 7e385fc126
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
snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
2026-09-18 01:22:23 +05:00

1553 lines
41 KiB
Go
Generated

package envoy
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
import (
"context"
"fmt"
"strconv"
"github.com/cortezaproject/corteza/server/compose/types"
"github.com/cortezaproject/corteza/server/pkg/envoyx"
"github.com/cortezaproject/corteza/server/pkg/expr"
"github.com/cortezaproject/corteza/server/pkg/id"
"github.com/cortezaproject/corteza/server/store"
"github.com/pkg/errors"
)
type (
// StoreEncoder is responsible for encoding Corteza resources into the
// database via the Storer or the DAL interface
//
// @todo consider having a different encoder for the DAL resources
StoreEncoder struct{}
)
// Prepare performs some initial processing on the resource before it can be encoded
//
// Preparation runs validation, default value initialization, matching with
// already existing instances, ...
//
// The prepare function receives a set of nodes grouped by the resource type.
// This enables some batching optimization and simplifications when it comes to
// matching with existing resources.
//
// Prepare does not receive any placeholder nodes which are used solely
// for dependency resolution.
func (e StoreEncoder) Prepare(ctx context.Context, p envoyx.EncodeParams, rt string, nn envoyx.NodeSet) (err error) {
s, err := e.grabStorer(p)
if err != nil {
return
}
switch rt {
case types.ChartResourceType:
return e.prepareChart(ctx, p, s, nn)
case types.ModuleResourceType:
return e.prepareModule(ctx, p, s, nn)
case types.ModuleFieldResourceType:
return e.prepareModuleField(ctx, p, s, nn)
case types.NamespaceResourceType:
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)
}
return
}
// Encode encodes the given Corteza resources into the primary store
//
// Encoding should not do any additional processing apart from matching with
// dependencies and runtime validation
//
// The Encode function is called for every resource type where the resource
// appears at the root of the dependency tree.
// All of the root-level resources for that resource type are passed into the function.
// The encoding function must traverse the branches to encode all of the dependencies.
//
// This flow is used to simplify the flow of how resources are encoded into YAML
// (and other documents) as well as to simplify batching.
//
// Encode does not receive any placeholder nodes which are used solely
// for dependency resolution.
func (e StoreEncoder) Encode(ctx context.Context, p envoyx.EncodeParams, rt string, nodes envoyx.NodeSet, tree envoyx.Traverser) (err error) {
s, err := e.grabStorer(p)
if err != nil {
return
}
switch rt {
case types.ChartResourceType:
return e.encodeCharts(ctx, p, s, nodes, tree)
case types.ModuleResourceType:
return e.encodeModules(ctx, p, s, nodes, tree)
case types.ModuleFieldResourceType:
return e.encodeModuleFields(ctx, p, s, nodes, tree)
case types.NamespaceResourceType:
return e.encodeNamespaces(ctx, p, s, nodes, tree)
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)
}
}
// // // // // // // // // // // // // // // // // // // // // // // // //
// Functions for resource chart
// // // // // // // // // // // // // // // // // // // // // // // // //
// prepareChart prepares the resources of the given type for encoding
func (e StoreEncoder) prepareChart(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.Chart, len(nn))
err = e.matchupCharts(ctx, s, existing, scopedNodes, nn)
if err != nil {
err = errors.Wrap(err, "failed to matchup existing Charts")
return
}
for i, n := range nn {
if n.Resource == nil {
panic("unexpected state: cannot call prepareChart with nodes without a defined Resource")
}
res, ok := n.Resource.(*types.Chart)
if !ok {
panic("unexpected resource type: node expecting type of chart")
}
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.setChartDefaults(res)
if err != nil {
return err
}
err = e.validateChart(res)
if err != nil {
return err
}
}
n.Resource = res
}
return
}
// encodeCharts encodes a set of resource into the database
func (e StoreEncoder) encodeCharts(ctx context.Context, p envoyx.EncodeParams, s store.Storer, nn envoyx.NodeSet, tree envoyx.Traverser) (err error) {
for _, n := range nn {
err = e.encodeChart(ctx, p, s, n, tree)
if err != nil {
return
}
}
return
}
// encodeChart encodes the resource into the database
func (e StoreEncoder) encodeChart(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 {
auxID = safeParentID(tree, n, ref)
if auxID == 0 {
continue
}
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.UpsertComposeChart(ctx, s, n.Resource.(*types.Chart))
if err != nil {
err = errors.Wrap(err, "failed to upsert Chart")
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
}
// matchupCharts returns an index with indicates what resources already exist
func (e StoreEncoder) matchupCharts(ctx context.Context, s store.Storer, uu map[int]types.Chart, 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.SearchComposeCharts(ctx, s, types.ChartFilter{})
if err != nil {
return
}
idMap := make(map[uint64]*types.Chart, len(aa))
strMap := make(map[string]*types.Chart, len(aa))
for _, a := range aa {
strMap[a.Handle] = a
idMap[a.ID] = a
}
var aux *types.Chart
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
}
// // // // // // // // // // // // // // // // // // // // // // // // //
// Functions for resource module
// // // // // // // // // // // // // // // // // // // // // // // // //
// prepareModule prepares the resources of the given type for encoding
func (e StoreEncoder) prepareModule(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.Module, len(nn))
err = e.matchupModules(ctx, s, existing, scopedNodes, nn)
if err != nil {
err = errors.Wrap(err, "failed to matchup existing Modules")
return
}
for i, n := range nn {
if n.Resource == nil {
panic("unexpected state: cannot call prepareModule with nodes without a defined Resource")
}
res, ok := n.Resource.(*types.Module)
if !ok {
panic("unexpected resource type: node expecting type of module")
}
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.setModuleDefaults(res)
if err != nil {
return err
}
err = e.validateModule(res)
if err != nil {
return err
}
}
n.Resource = res
}
return
}
// encodeModules encodes a set of resource into the database
func (e StoreEncoder) encodeModules(ctx context.Context, p envoyx.EncodeParams, s store.Storer, nn envoyx.NodeSet, tree envoyx.Traverser) (err error) {
for _, n := range nn {
err = e.encodeModule(ctx, p, s, n, tree)
if err != nil {
return
}
}
err = e.postModulesEncode(ctx, p, s, tree, nn)
return
}
// encodeModule encodes the resource into the database
func (e StoreEncoder) encodeModule(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 {
auxID = safeParentID(tree, n, ref)
if auxID == 0 {
continue
}
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.UpsertComposeModule(ctx, s, n.Resource.(*types.Module))
if err != nil {
err = errors.Wrap(err, "failed to upsert Module")
return
}
}
// Handle resources nested under it
//
// @todo how can we remove the OmitPlaceholderNodes call the same way we did for
// the root function calls?
nested := make(envoyx.NodeSet, 0, 10)
err = func() (err error) {
for rt, nn := range envoyx.NodesByResourceType(tree.Children(n)...) {
nn = envoyx.OmitPlaceholderNodes(nn...)
switch rt {
case types.ModuleFieldResourceType:
err = e.encodeModuleFields(ctx, p, s, nn, tree)
if err != nil {
return
}
nested = append(nested, nn...)
}
}
return
}()
if err != nil {
err = errors.Wrap(err, "failed to encode nested resources")
return
}
err = e.encodeModuleExtend(ctx, p, s, n, nested, tree)
if err != nil {
err = errors.Wrap(err, "post encode logic failed with errors")
return
}
return
}
// matchupModules returns an index with indicates what resources already exist
func (e StoreEncoder) matchupModules(ctx context.Context, s store.Storer, uu map[int]types.Module, 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.SearchComposeModules(ctx, s, types.ModuleFilter{})
if err != nil {
return
}
idMap := make(map[uint64]*types.Module, len(aa))
strMap := make(map[string]*types.Module, len(aa))
for _, a := range aa {
strMap[a.Handle] = a
idMap[a.ID] = a
}
var aux *types.Module
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
}
// // // // // // // // // // // // // // // // // // // // // // // // //
// Functions for resource moduleField
// // // // // // // // // // // // // // // // // // // // // // // // //
// prepareModuleField prepares the resources of the given type for encoding
func (e StoreEncoder) prepareModuleField(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.ModuleField, len(nn))
err = e.matchupModuleFields(ctx, s, existing, scopedNodes, nn)
if err != nil {
err = errors.Wrap(err, "failed to matchup existing ModuleFields")
return
}
for i, n := range nn {
if n.Resource == nil {
panic("unexpected state: cannot call prepareModuleField with nodes without a defined Resource")
}
res, ok := n.Resource.(*types.ModuleField)
if !ok {
panic("unexpected resource type: node expecting type of moduleField")
}
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.setModuleFieldDefaults(res)
if err != nil {
return err
}
err = e.validateModuleField(res)
if err != nil {
return err
}
}
n.Resource = res
}
return
}
// encodeModuleFields encodes a set of resource into the database
func (e StoreEncoder) encodeModuleFields(ctx context.Context, p envoyx.EncodeParams, s store.Storer, nn envoyx.NodeSet, tree envoyx.Traverser) (err error) {
for _, n := range nn {
err = e.encodeModuleField(ctx, p, s, n, tree)
if err != nil {
return
}
}
return
}
// encodeModuleField encodes the resource into the database
func (e StoreEncoder) encodeModuleField(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 {
auxID = safeParentID(tree, n, ref)
if auxID == 0 {
continue
}
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
}
// Custom resource sanitization before saving.
// This can be used to cleanup arbitrary config fields.
e.sanitizeModuleFieldBeforeSave(n.Resource.(*types.ModuleField))
// Flush to the DB
if !n.Evaluated.Skip {
err = store.UpsertComposeModuleField(ctx, s, n.Resource.(*types.ModuleField))
if err != nil {
err = errors.Wrap(err, "failed to upsert ModuleField")
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
}
// matchupModuleFields returns an index with indicates what resources already exist
func (e StoreEncoder) matchupModuleFields(ctx context.Context, s store.Storer, uu map[int]types.ModuleField, 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.SearchComposeModuleFields(ctx, s, types.ModuleFieldFilter{})
if err != nil {
return
}
idMap := make(map[uint64]*types.ModuleField, len(aa))
strMap := make(map[string]*types.ModuleField, len(aa))
for _, a := range aa {
idMap[a.ID] = a
strMap[a.Name] = a
}
var aux *types.ModuleField
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
}
// // // // // // // // // // // // // // // // // // // // // // // // //
// Functions for resource namespace
// // // // // // // // // // // // // // // // // // // // // // // // //
// prepareNamespace prepares the resources of the given type for encoding
func (e StoreEncoder) prepareNamespace(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.Namespace, len(nn))
err = e.matchupNamespaces(ctx, s, existing, scopedNodes, nn)
if err != nil {
err = errors.Wrap(err, "failed to matchup existing Namespaces")
return
}
for i, n := range nn {
if n.Resource == nil {
panic("unexpected state: cannot call prepareNamespace with nodes without a defined Resource")
}
res, ok := n.Resource.(*types.Namespace)
if !ok {
panic("unexpected resource type: node expecting type of namespace")
}
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.setNamespaceDefaults(res)
if err != nil {
return err
}
err = e.validateNamespace(res)
if err != nil {
return err
}
}
n.Resource = res
}
return
}
// encodeNamespaces encodes a set of resource into the database
func (e StoreEncoder) encodeNamespaces(ctx context.Context, p envoyx.EncodeParams, s store.Storer, nn envoyx.NodeSet, tree envoyx.Traverser) (err error) {
for _, n := range nn {
err = e.encodeNamespace(ctx, p, s, n, tree)
if err != nil {
return
}
}
return
}
// encodeNamespace encodes the resource into the database
func (e StoreEncoder) encodeNamespace(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 {
auxID = safeParentID(tree, n, ref)
if auxID == 0 {
continue
}
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.UpsertComposeNamespace(ctx, s, n.Resource.(*types.Namespace))
if err != nil {
err = errors.Wrap(err, "failed to upsert Namespace")
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 {
case types.ChartResourceType:
err = e.encodeCharts(ctx, p, s, nn, tree)
if err != nil {
return
}
case types.ModuleResourceType:
err = e.encodeModules(ctx, p, s, nn, tree)
if err != nil {
return
}
case types.PageResourceType:
err = e.encodePages(ctx, p, s, nn, tree)
if err != nil {
return
}
}
}
return
}()
if err != nil {
err = errors.Wrap(err, "failed to encode nested resources")
return
}
return
}
// matchupNamespaces returns an index with indicates what resources already exist
func (e StoreEncoder) matchupNamespaces(ctx context.Context, s store.Storer, uu map[int]types.Namespace, 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.SearchComposeNamespaces(ctx, s, types.NamespaceFilter{})
if err != nil {
return
}
idMap := make(map[uint64]*types.Namespace, len(aa))
strMap := make(map[string]*types.Namespace, len(aa))
for _, a := range aa {
idMap[a.ID] = a
strMap[a.Slug] = a
}
var aux *types.Namespace
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
}
// // // // // // // // // // // // // // // // // // // // // // // // //
// Functions for resource page
// // // // // // // // // // // // // // // // // // // // // // // // //
// preparePage prepares the resources of the given type for encoding
func (e StoreEncoder) preparePage(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.Page, len(nn))
err = e.matchupPages(ctx, s, existing, scopedNodes, nn)
if err != nil {
err = errors.Wrap(err, "failed to matchup existing Pages")
return
}
for i, n := range nn {
if n.Resource == nil {
panic("unexpected state: cannot call preparePage with nodes without a defined Resource")
}
res, ok := n.Resource.(*types.Page)
if !ok {
panic("unexpected resource type: node expecting type of page")
}
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.setPageDefaults(res)
if err != nil {
return err
}
err = e.validatePage(res)
if err != nil {
return err
}
}
n.Resource = res
}
return
}
// encodePages encodes a set of resource into the database
func (e StoreEncoder) encodePages(ctx context.Context, p envoyx.EncodeParams, s store.Storer, nn envoyx.NodeSet, tree envoyx.Traverser) (err error) {
for _, n := range nn {
err = e.encodePage(ctx, p, s, n, tree)
if err != nil {
return
}
}
return
}
// encodePage encodes the resource into the database
func (e StoreEncoder) encodePage(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 {
auxID = safeParentID(tree, n, ref)
if auxID == 0 {
continue
}
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.UpsertComposePage(ctx, s, n.Resource.(*types.Page))
if err != nil {
err = errors.Wrap(err, "failed to upsert Page")
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 {
case types.PageLayoutResourceType:
err = e.encodePageLayouts(ctx, p, s, nn, tree)
if err != nil {
return
}
}
}
return
}()
if err != nil {
err = errors.Wrap(err, "failed to encode nested resources")
return
}
return
}
// matchupPages returns an index with indicates what resources already exist
func (e StoreEncoder) matchupPages(ctx context.Context, s store.Storer, uu map[int]types.Page, 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.SearchComposePages(ctx, s, types.PageFilter{})
if err != nil {
return
}
idMap := make(map[uint64]*types.Page, len(aa))
strMap := make(map[string]*types.Page, len(aa))
for _, a := range aa {
strMap[a.Handle] = a
idMap[a.ID] = a
}
var aux *types.Page
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
}
// // // // // // // // // // // // // // // // // // // // // // // // //
// 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 {
auxID = safeParentID(tree, n, ref)
if auxID == 0 {
continue
}
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
// // // // // // // // // // // // // // // // // // // // // // // // //
func (e *StoreEncoder) grabStorer(p envoyx.EncodeParams) (s store.Storer, err error) {
auxs, ok := p.Params[paramsKeyStorer]
if !ok {
err = errors.Errorf("store encoder expects a store conforming to store.Storer interface")
return
}
s, ok = auxs.(store.Storer)
if !ok {
err = errors.Errorf("store encoder expects a store conforming to store.Storer interface")
return
}
return
}
func (e *StoreEncoder) runEvals(ctx context.Context, existing bool, n *envoyx.Node) (err error) {
// Skip if
if n.Config.SkipIfEval == nil {
return
}
aux, err := expr.EmptyVars().Cast(map[string]any{
"missing": !existing,
})
if err != nil {
return
}
n.Evaluated.Skip, err = n.Config.SkipIfEval.Test(ctx, aux.(*expr.Vars))
return
}
func (e StoreEncoder) getScopeNodes(ctx context.Context, s store.Storer, nn envoyx.NodeSet) (scopes envoyx.NodeSet, err error) {
// Get all requested scopes
scopes = make(envoyx.NodeSet, len(nn))
err = func() (err error) {
for i, n := range nn {
if n.Scope.ResourceType == "" {
continue
}
// For now the scope can only point to namespace so this will do
var nn envoyx.NodeSet
nn, err = e.decodeNamespace(ctx, s, e.makeNamespaceFilter(nil, nil, envoyx.ResourceFilter{Identifiers: n.Scope.Identifiers}))
if err != nil {
return
}
if len(nn) > 1 {
err = fmt.Errorf("ambiguous scope %v: matches multiple resources", n.Scope)
return
}
// when encoding, it could be missing
if len(nn) == 0 {
return
}
scopes[i] = nn[0]
}
return
}()
if err != nil {
err = errors.Wrap(err, "failed to decode node scopes")
return
}
return
}
func (e StoreEncoder) decodeNamespace(ctx context.Context, s store.Storer, f types.NamespaceFilter) (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.SearchComposeNamespaces(ctx, s, f)
if err != nil {
return
}
for _, r := range rr {
var n *envoyx.Node
n, err = NamespaceToEnvoyNode(r)
if err != nil {
return
}
out = append(out, n)
}
return
}
func (e StoreEncoder) makeNamespaceFilter(scope *envoyx.Node, refs map[string]*envoyx.Node, auxf envoyx.ResourceFilter) (out types.NamespaceFilter) {
out.Limit = auxf.Limit
ids, hh := auxf.Identifiers.Idents()
_ = ids
_ = hh
out.NamespaceID = id.Strings(ids...)
if len(hh) > 0 {
out.Slug = hh[0]
}
if scope == nil {
return
}
if scope.ResourceType == "" {
return
}
// Overwrite it
out.NamespaceID = id.Strings(scope.Resource.GetID())
return
}
func safeParentID(tt envoyx.Traverser, n *envoyx.Node, ref envoyx.Ref) (out uint64) {
rn := tt.ParentForRef(n, ref)
if rn == nil {
return
}
return rn.Resource.GetID()
}