284 lines
5.8 KiB
Go
284 lines
5.8 KiB
Go
package envoyx
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/cortezaproject/corteza/server/pkg/expr"
|
|
)
|
|
|
|
type (
|
|
service struct {
|
|
decoders map[decodeType][]Decoder
|
|
|
|
encoders map[encodeType][]Encoder
|
|
preparers map[encodeType][]Preparer
|
|
}
|
|
|
|
// Traverser provides a structure which can be used to traverse the node's deps
|
|
Traverser interface {
|
|
// ParentForRef returns the parent of the provided node which matches the ref
|
|
//
|
|
// If no parent is found, nil is returned.
|
|
ParentForRef(*Node, Ref) *Node
|
|
|
|
// ChildrenForResourceType returns the children of the provided node which
|
|
// match the provided resource type
|
|
ChildrenForResourceType(*Node, string) NodeSet
|
|
|
|
// Children returns all of the children of the provided node
|
|
Children(*Node) NodeSet
|
|
}
|
|
|
|
Preparer interface {
|
|
// Prepare performs generic preprocessing on the provided nodes
|
|
//
|
|
// The function is called for every resource type where all of the nodes of
|
|
// that resource type are passed as the argument.
|
|
Prepare(context.Context, EncodeParams, string, NodeSet) error
|
|
}
|
|
|
|
Encoder interface {
|
|
// Encode encodes the data
|
|
//
|
|
// The function receives a set of root-level nodes (with no parent dependencies)
|
|
// and a Traverser it can use to handle all of the child nodes.
|
|
Encode(context.Context, EncodeParams, string, NodeSet, Traverser) (err error)
|
|
}
|
|
|
|
PrepareEncoder interface {
|
|
Preparer
|
|
Encoder
|
|
}
|
|
|
|
Decoder interface {
|
|
// Decode returns a set of Nodes extracted based on the provided definition
|
|
Decode(ctx context.Context, p DecodeParams) (out NodeSet, err error)
|
|
}
|
|
|
|
DecodeParams struct {
|
|
Type decodeType
|
|
Params map[string]any
|
|
Config DecoderConfig
|
|
Filter map[string]ResourceFilter
|
|
}
|
|
DecoderConfig struct{}
|
|
|
|
EncodeParams struct {
|
|
Type encodeType
|
|
Params map[string]any
|
|
Envoy EnvoyConfig
|
|
Encoder EncoderConfig
|
|
}
|
|
EncoderConfig struct {
|
|
PreferredTimeLayout string
|
|
PreferredTimezone string
|
|
}
|
|
|
|
ResourceFilter struct {
|
|
Identifiers Identifiers
|
|
Refs map[string]Ref
|
|
|
|
Limit uint
|
|
Scope Scope
|
|
}
|
|
|
|
decodeType string
|
|
encodeType string
|
|
mergeAlg int
|
|
)
|
|
|
|
var (
|
|
global *service
|
|
|
|
ex = expr.NewParser()
|
|
)
|
|
|
|
const (
|
|
OnConflictDefault mergeAlg = iota
|
|
OnConflictReplace
|
|
OnConflictSkip
|
|
OnConflictPanic
|
|
// OnConflictMergeLeft mergeAlg = "mergeLeft"
|
|
// OnConflictMergeRight mergeAlg = "mergeRight"
|
|
|
|
DecodeTypeURI decodeType = "uri"
|
|
DecodeTypeStore decodeType = "store"
|
|
|
|
EncodeTypeURI encodeType = "uri"
|
|
EncodeTypeStore encodeType = "store"
|
|
EncodeTypeIo encodeType = "io"
|
|
)
|
|
|
|
// New initializes a new Envoy service
|
|
func New() *service {
|
|
return &service{}
|
|
}
|
|
|
|
// SetGlobal sets the global envoy service
|
|
func SetGlobal(n *service) {
|
|
global = n
|
|
}
|
|
|
|
// Service gets the global envoy service
|
|
func Service() *service {
|
|
if global == nil {
|
|
panic("global service not defined")
|
|
}
|
|
|
|
return global
|
|
}
|
|
|
|
// Decode returns a set of envoy Nodes based on the given decode params
|
|
func (svc *service) Decode(ctx context.Context, p DecodeParams) (nn NodeSet, err error) {
|
|
err = p.validate()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
switch p.Type {
|
|
case DecodeTypeURI:
|
|
return svc.decodeUri(ctx, p)
|
|
case DecodeTypeStore:
|
|
return svc.decodeStore(ctx, p)
|
|
default:
|
|
err = fmt.Errorf("unsupported decoder type %s", p.Type)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (svc *service) Bake(ctx context.Context, p EncodeParams, nodes ...*Node) (err error) {
|
|
err = svc.bakeEnvoyConfig(p.Envoy, nodes...)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
err = svc.bakeExpressions(nodes...)
|
|
return
|
|
}
|
|
|
|
// Encode encodes Corteza resources bases on the provided encode params
|
|
//
|
|
// use the BuildDepGraph function to build the default dependency graph.
|
|
func (svc *service) Encode(ctx context.Context, p EncodeParams, dg *depGraph) (err error) {
|
|
err = p.validate()
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
switch p.Type {
|
|
case EncodeTypeStore:
|
|
return svc.encodeStore(ctx, dg, p)
|
|
case EncodeTypeIo:
|
|
return svc.encodeIo(ctx, dg, p)
|
|
default:
|
|
err = fmt.Errorf("unsupported encoder type %s", p.Type)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (svc *service) AddDecoder(t decodeType, dd ...Decoder) {
|
|
if svc.decoders == nil {
|
|
svc.decoders = make(map[decodeType][]Decoder)
|
|
}
|
|
svc.decoders[t] = append(svc.decoders[t], dd...)
|
|
}
|
|
|
|
func (svc *service) AddEncoder(t encodeType, ee ...Encoder) {
|
|
if svc.encoders == nil {
|
|
svc.encoders = make(map[encodeType][]Encoder)
|
|
}
|
|
svc.encoders[t] = append(svc.encoders[t], ee...)
|
|
}
|
|
|
|
func (svc *service) AddPreparer(t encodeType, pp ...Preparer) {
|
|
if svc.preparers == nil {
|
|
svc.preparers = make(map[encodeType][]Preparer)
|
|
}
|
|
svc.preparers[t] = append(svc.preparers[t], pp...)
|
|
}
|
|
|
|
// Utility functions
|
|
|
|
func (p DecodeParams) validate() (err error) {
|
|
switch p.Type {
|
|
case DecodeTypeURI:
|
|
_, ok := p.Params["uri"]
|
|
if !ok {
|
|
return fmt.Errorf("uhoh, no uri provided")
|
|
}
|
|
|
|
case DecodeTypeStore:
|
|
|
|
}
|
|
|
|
// @todo...
|
|
|
|
return
|
|
}
|
|
|
|
func (p EncodeParams) validate() (err error) {
|
|
switch p.Type {
|
|
// @todo...
|
|
}
|
|
|
|
// @todo...
|
|
|
|
return
|
|
}
|
|
|
|
func CastMergeAlg(v string) (mergeAlg mergeAlg) {
|
|
switch strings.ToLower(v) {
|
|
case "replace", "mergeleft":
|
|
mergeAlg = OnConflictReplace
|
|
case "skip", "mergeright":
|
|
mergeAlg = OnConflictSkip
|
|
case "panic", "error":
|
|
mergeAlg = OnConflictPanic
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (svc *service) bakeEnvoyConfig(dft EnvoyConfig, nodes ...*Node) (err error) {
|
|
for _, n := range nodes {
|
|
n.Config = svc.mergeEnvoyConfigs(n.Config, dft)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (svc *service) bakeExpressions(nodes ...*Node) (err error) {
|
|
for _, n := range nodes {
|
|
if n.Config.SkipIf == "" {
|
|
continue
|
|
}
|
|
|
|
n.Config.SkipIfEval, err = ex.Parse(n.Config.SkipIf)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (svc *service) mergeEnvoyConfigs(a, b EnvoyConfig) (c EnvoyConfig) {
|
|
c = a
|
|
if c.MergeAlg == OnConflictDefault {
|
|
c.MergeAlg = b.MergeAlg
|
|
}
|
|
if c.MergeAlg == OnConflictDefault {
|
|
c.MergeAlg = OnConflictReplace
|
|
}
|
|
|
|
// @todo pre eval this?
|
|
if c.SkipIf == "" {
|
|
c.SkipIf = b.SkipIf
|
|
}
|
|
|
|
return
|
|
}
|