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
1038 lines
26 KiB
Go
Generated
1038 lines
26 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"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/cortezaproject/corteza/server/automation/types"
|
|
"github.com/cortezaproject/corteza/server/pkg/envoyx"
|
|
"github.com/cortezaproject/corteza/server/pkg/rbac"
|
|
"github.com/cortezaproject/corteza/server/pkg/y7s"
|
|
systemTypes "github.com/cortezaproject/corteza/server/system/types"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cast"
|
|
"golang.org/x/text/language"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type (
|
|
// YamlDecoder is responsible for decoding YAML documents into Corteza resources
|
|
// which are then managed by envoy and imported via an encoder.
|
|
YamlDecoder struct{}
|
|
|
|
// documentContext provides a bit of metadata to the decoder such as
|
|
// root-level reference definitions (such as the namespace)
|
|
documentContext struct {
|
|
// references holds any references defined on the root of the document
|
|
//
|
|
// Example of defining a namespace reference:
|
|
// namespace: test_import_namespace
|
|
// modules:
|
|
// - name: Test Import Module
|
|
// handle: test_import_module
|
|
references map[string]string
|
|
|
|
// parentIdent holds the identifier of the parent resource (if nested).
|
|
// The parent ident can be handy if the resource requires the info before the
|
|
// original handling is finished (such as record datasource nodes).
|
|
parentIdent envoyx.Identifiers
|
|
}
|
|
|
|
// auxYamlDoc is a helper struct that registers custom YAML decoders
|
|
// to assist the package
|
|
auxYamlDoc struct {
|
|
nodes envoyx.NodeSet
|
|
}
|
|
)
|
|
|
|
const (
|
|
paramsKeyStream = "stream"
|
|
)
|
|
|
|
// CanFile returns true if the provided file can be decoded with this decoder
|
|
func (d YamlDecoder) CanFile(f *os.File) (ok bool) {
|
|
// @todo improve this check; for now a simple extension check should do the trick
|
|
return d.CanExt(f.Name())
|
|
}
|
|
|
|
// CanExt returns true if the provided file extension can be decoded with this decoder
|
|
func (d YamlDecoder) CanExt(name string) (ok bool) {
|
|
var (
|
|
pt = strings.Split(name, ".")
|
|
ext = strings.TrimSpace(pt[len(pt)-1])
|
|
)
|
|
return ext == "yaml" || ext == "yml"
|
|
}
|
|
|
|
// Decode returns a set of envoy nodes based on the provided params
|
|
//
|
|
// YamlDecoder expects the DecodeParam of `stream` which conforms
|
|
// to the io.Reader interface.
|
|
func (d YamlDecoder) Decode(ctx context.Context, p envoyx.DecodeParams) (out envoyx.NodeSet, err error) {
|
|
r, err := d.getReader(ctx, p)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Offload decoding to the aux document
|
|
doc := &auxYamlDoc{}
|
|
err = yaml.NewDecoder(r).Decode(doc)
|
|
if err != nil {
|
|
err = errors.Wrap(err, "automation yaml decoder: failed to decode document")
|
|
return
|
|
}
|
|
|
|
return doc.nodes, nil
|
|
}
|
|
|
|
func (d *auxYamlDoc) UnmarshalYAML(n *yaml.Node) (err error) {
|
|
// Get the document context from the root level
|
|
dctx, err := d.getDocumentContext(n)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
var aux envoyx.NodeSet
|
|
return y7s.EachMap(n, func(k, v *yaml.Node) error {
|
|
kv := strings.ToLower(k.Value)
|
|
|
|
switch kv {
|
|
// Decode all resources under the automation component
|
|
case "workflow", "workflows":
|
|
if y7s.IsMapping(v) {
|
|
aux, err = d.unmarshalWorkflowMap(dctx, v)
|
|
d.nodes = append(d.nodes, aux...)
|
|
}
|
|
|
|
if y7s.IsSeq(v) {
|
|
aux, err = d.unmarshalWorkflowSeq(dctx, v)
|
|
d.nodes = append(d.nodes, aux...)
|
|
}
|
|
if err != nil {
|
|
err = errors.Wrap(err, "failed to unmarshal node: workflow")
|
|
}
|
|
return err
|
|
|
|
case "trigger":
|
|
// @note trigger doesn't support mapped inputs. This can be
|
|
// changed in the .cue definition under the
|
|
// .envoy.yaml.supportMappedInput field.
|
|
if y7s.IsSeq(v) {
|
|
aux, err = d.unmarshalTriggerSeq(dctx, v)
|
|
d.nodes = append(d.nodes, aux...)
|
|
}
|
|
if err != nil {
|
|
err = errors.Wrap(err, "failed to unmarshal node: trigger")
|
|
}
|
|
return err
|
|
|
|
// Offload to custom handlers
|
|
default:
|
|
aux, err = d.unmarshalYAML(kv, v)
|
|
d.nodes = append(d.nodes, aux...)
|
|
if err != nil {
|
|
err = errors.Wrap(err, "failed to unmarshal node")
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
} // // // // // // // // // // // // // // // // // // // // // // // // //
|
|
// Functions for resource workflow
|
|
// // // // // // // // // // // // // // // // // // // // // // // // //
|
|
|
|
// unmarshalWorkflowSeq unmarshals Workflow when provided as a sequence node
|
|
func (d *auxYamlDoc) unmarshalWorkflowSeq(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.unmarshalWorkflowNode(dctx, n)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out = append(out, aux...)
|
|
|
|
return nil
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
// unmarshalWorkflowMap unmarshals Workflow 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) unmarshalWorkflowMap(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.unmarshalWorkflowNode(dctx, n, k)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out = append(out, aux...)
|
|
|
|
return nil
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
// unmarshalTriggersExtendedSeq unmarshals Triggers when provided as a sequence node
|
|
func (d *auxYamlDoc) unmarshalExtendedTriggersSeq(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.unmarshalTriggersExtendedNode(dctx, n)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out = append(out, aux...)
|
|
|
|
return nil
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
// unmarshalWorkflowNode is a cookie-cutter function to unmarshal
|
|
// the yaml node into the corresponding Corteza type & Node
|
|
func (d *auxYamlDoc) unmarshalWorkflowNode(dctx documentContext, n *yaml.Node, meta ...*yaml.Node) (out envoyx.NodeSet, err error) {
|
|
var r *types.Workflow
|
|
|
|
// @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 "createdby":
|
|
// Handle references
|
|
err = y7s.DecodeScalar(n, "createdBy", &auxNodeValue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Omit if not defined
|
|
tmp := cast.ToString(auxNodeValue)
|
|
if tmp == "0" || tmp == "" {
|
|
break
|
|
}
|
|
refs["CreatedBy"] = envoyx.Ref{
|
|
ResourceType: "corteza::system:user",
|
|
Identifiers: envoyx.MakeIdentifiers(auxNodeValue),
|
|
}
|
|
|
|
break
|
|
|
|
case "deletedby":
|
|
// Handle references
|
|
err = y7s.DecodeScalar(n, "deletedBy", &auxNodeValue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Omit if not defined
|
|
tmp := cast.ToString(auxNodeValue)
|
|
if tmp == "0" || tmp == "" {
|
|
break
|
|
}
|
|
refs["DeletedBy"] = envoyx.Ref{
|
|
ResourceType: "corteza::system:user",
|
|
Identifiers: envoyx.MakeIdentifiers(auxNodeValue),
|
|
}
|
|
|
|
break
|
|
|
|
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 "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 "runas":
|
|
// Handle references
|
|
err = y7s.DecodeScalar(n, "runAs", &auxNodeValue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Omit if not defined
|
|
tmp := cast.ToString(auxNodeValue)
|
|
if tmp == "0" || tmp == "" {
|
|
break
|
|
}
|
|
refs["RunAs"] = envoyx.Ref{
|
|
ResourceType: "corteza::system:user",
|
|
Identifiers: envoyx.MakeIdentifiers(auxNodeValue),
|
|
}
|
|
|
|
break
|
|
|
|
case "updatedby":
|
|
// Handle references
|
|
err = y7s.DecodeScalar(n, "updatedBy", &auxNodeValue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Omit if not defined
|
|
tmp := cast.ToString(auxNodeValue)
|
|
if tmp == "0" || tmp == "" {
|
|
break
|
|
}
|
|
refs["UpdatedBy"] = envoyx.Ref{
|
|
ResourceType: "corteza::system:user",
|
|
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
|
|
|
|
// 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 workflow 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) {
|
|
|
|
case "triggers":
|
|
if y7s.IsSeq(n) {
|
|
nestedNodes, err = d.unmarshalExtendedTriggersSeq(dctx, n)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
break
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
if _, ok := a.References["WorkflowID"]; !ok {
|
|
a.References["WorkflowID"] = envoyx.Ref{
|
|
ResourceType: types.WorkflowResourceType,
|
|
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.WorkflowResourceType,
|
|
Identifiers: ii,
|
|
References: refs,
|
|
|
|
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["0"] = 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
|
|
}
|
|
|
|
// // // // // // // // // // // // // // // // // // // // // // // // //
|
|
// Functions for resource trigger
|
|
// // // // // // // // // // // // // // // // // // // // // // // // //
|
|
|
|
// unmarshalTriggerSeq unmarshals Trigger when provided as a sequence node
|
|
func (d *auxYamlDoc) unmarshalTriggerSeq(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.unmarshalTriggerNode(dctx, n)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
out = append(out, aux...)
|
|
|
|
return nil
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
// unmarshalTriggerMap unmarshals Trigger 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
|
|
// @note this resource does not support map encoding.
|
|
// Refer to the corresponding definition files to adjust if needed.
|
|
|
|
// unmarshalTriggerNode is a cookie-cutter function to unmarshal
|
|
// the yaml node into the corresponding Corteza type & Node
|
|
func (d *auxYamlDoc) unmarshalTriggerNode(dctx documentContext, n *yaml.Node, meta ...*yaml.Node) (out envoyx.NodeSet, err error) {
|
|
var r *types.Trigger
|
|
|
|
// @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{}
|
|
|
|
var (
|
|
refs = make(map[string]envoyx.Ref)
|
|
auxOut envoyx.NodeSet
|
|
nestedNodes envoyx.NodeSet
|
|
scope envoyx.Scope
|
|
envoyConfig envoyx.EnvoyConfig
|
|
)
|
|
_ = auxOut
|
|
_ = refs
|
|
|
|
err = y7s.EachMap(n, func(k, n *yaml.Node) error {
|
|
var auxNodeValue any
|
|
_ = auxNodeValue
|
|
|
|
switch strings.ToLower(k.Value) {
|
|
|
|
case "createdby":
|
|
// Handle references
|
|
err = y7s.DecodeScalar(n, "createdBy", &auxNodeValue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Omit if not defined
|
|
tmp := cast.ToString(auxNodeValue)
|
|
if tmp == "0" || tmp == "" {
|
|
break
|
|
}
|
|
refs["CreatedBy"] = envoyx.Ref{
|
|
ResourceType: "corteza::system:user",
|
|
Identifiers: envoyx.MakeIdentifiers(auxNodeValue),
|
|
}
|
|
|
|
break
|
|
|
|
case "deletedby":
|
|
// Handle references
|
|
err = y7s.DecodeScalar(n, "deletedBy", &auxNodeValue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Omit if not defined
|
|
tmp := cast.ToString(auxNodeValue)
|
|
if tmp == "0" || tmp == "" {
|
|
break
|
|
}
|
|
refs["DeletedBy"] = envoyx.Ref{
|
|
ResourceType: "corteza::system:user",
|
|
Identifiers: envoyx.MakeIdentifiers(auxNodeValue),
|
|
}
|
|
|
|
break
|
|
|
|
case "eventtype", "eventType", "event_type":
|
|
// Handle field alias
|
|
//
|
|
// @todo consider adding an is empty check before overwriting
|
|
err = y7s.DecodeScalar(n, "eventType", &r.EventType)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
break
|
|
|
|
case "id":
|
|
// Handle identifiers
|
|
err = y7s.DecodeScalar(n, "id", &auxNodeValue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ii = ii.Add(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 "resourcetype", "resourceType", "resource_type":
|
|
// Handle field alias
|
|
//
|
|
// @todo consider adding an is empty check before overwriting
|
|
err = y7s.DecodeScalar(n, "resourceType", &r.ResourceType)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
break
|
|
|
|
case "stepid", "stepID", "step_id":
|
|
// Handle field alias
|
|
//
|
|
// @todo consider adding an is empty check before overwriting
|
|
err = y7s.DecodeScalar(n, "stepID", &r.StepID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
break
|
|
|
|
case "updatedby":
|
|
// Handle references
|
|
err = y7s.DecodeScalar(n, "updatedBy", &auxNodeValue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Omit if not defined
|
|
tmp := cast.ToString(auxNodeValue)
|
|
if tmp == "0" || tmp == "" {
|
|
break
|
|
}
|
|
refs["UpdatedBy"] = envoyx.Ref{
|
|
ResourceType: "corteza::system:user",
|
|
Identifiers: envoyx.MakeIdentifiers(auxNodeValue),
|
|
}
|
|
|
|
break
|
|
|
|
case "workflowid":
|
|
// Handle references
|
|
err = y7s.DecodeScalar(n, "workflowID", &auxNodeValue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Omit if not defined
|
|
tmp := cast.ToString(auxNodeValue)
|
|
if tmp == "0" || tmp == "" {
|
|
break
|
|
}
|
|
refs["WorkflowID"] = envoyx.Ref{
|
|
ResourceType: "corteza::automation:workflow",
|
|
Identifiers: envoyx.MakeIdentifiers(auxNodeValue),
|
|
}
|
|
|
|
break
|
|
|
|
case "(envoy)":
|
|
envoyConfig = d.decodeEnvoyConfig(n)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Make parent identifiers available through the dctx
|
|
dctx.parentIdent = ii
|
|
|
|
// 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 trigger 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)
|
|
}
|
|
|
|
if _, ok := a.References["TriggerID"]; !ok {
|
|
a.References["TriggerID"] = envoyx.Ref{
|
|
ResourceType: types.TriggerResourceType,
|
|
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.TriggerResourceType,
|
|
Identifiers: ii,
|
|
References: refs,
|
|
|
|
Config: envoyConfig,
|
|
}
|
|
|
|
// Put it all together...
|
|
out = append(out, a)
|
|
out = append(out, auxOut...)
|
|
|
|
return
|
|
}
|
|
|
|
// // // // // // // // // // // // // // // // // // // // // // // // //
|
|
// RBAC unmarshal logic
|
|
// // // // // // // // // // // // // // // // // // // // // // // // //
|
|
|
|
func unmarshalAllowNode(n *yaml.Node) (out envoyx.NodeSet, err error) {
|
|
return unmarshalRBACNode(n, rbac.Allow)
|
|
}
|
|
|
|
func unmarshalDenyNode(n *yaml.Node) (out envoyx.NodeSet, err error) {
|
|
return unmarshalRBACNode(n, rbac.Deny)
|
|
}
|
|
|
|
func unmarshalRBACNode(n *yaml.Node, acc rbac.Access) (out envoyx.NodeSet, err error) {
|
|
if y7s.IsMapping(n.Content[1]) {
|
|
out, err = unmarshalFlatRBACNode(n, acc)
|
|
if err != nil {
|
|
return
|
|
}
|
|
} else {
|
|
out, err = unmarshalNestedRBACNode(n, acc)
|
|
if err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
for _, o := range out {
|
|
for _, r := range o.References {
|
|
if r.Scope.IsEmpty() {
|
|
continue
|
|
}
|
|
o.Scope = r.Scope
|
|
break
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// unmarshalNestedRBACNode handles RBAC rules when they are nested inside a resource
|
|
//
|
|
// The edge-case exists since the node doesn't explicitly specify the resource
|
|
// it belongs to.
|
|
//
|
|
// Example:
|
|
//
|
|
// modules:
|
|
//
|
|
// module1:
|
|
// name: "module 1"
|
|
// fields: ...
|
|
// allow:
|
|
// role1:
|
|
// - read
|
|
// - delete
|
|
func unmarshalNestedRBACNode(n *yaml.Node, acc rbac.Access) (out envoyx.NodeSet, err error) {
|
|
return out, y7s.EachMap(n, func(role, op *yaml.Node) error {
|
|
out = append(out, &envoyx.Node{
|
|
Resource: &rbac.Rule{
|
|
Operation: op.Value,
|
|
Access: acc,
|
|
},
|
|
ResourceType: rbac.RuleResourceType,
|
|
References: map[string]envoyx.Ref{
|
|
"RoleID": {
|
|
// Providing resource type as plain text to reduce cross component references
|
|
ResourceType: "corteza::system:role",
|
|
Identifiers: envoyx.MakeIdentifiers(role.Value),
|
|
},
|
|
},
|
|
})
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// unmarshalFlatRBACNode handles RBAC rules when they are provided on the root level
|
|
//
|
|
// Example:
|
|
//
|
|
// allow:
|
|
//
|
|
// role1:
|
|
// corteza::system/:
|
|
// - users.search
|
|
// - users.create
|
|
func unmarshalFlatRBACNode(n *yaml.Node, acc rbac.Access) (out envoyx.NodeSet, err error) {
|
|
// Handles role
|
|
return out, y7s.EachMap(n, func(role, perm *yaml.Node) error {
|
|
// Handles operations
|
|
return y7s.EachMap(perm, func(res, ops *yaml.Node) error {
|
|
// Handle operation (one RBAC rule per op)
|
|
return y7s.EachSeq(ops, func(op *yaml.Node) error {
|
|
out = append(out, &envoyx.Node{
|
|
Resource: &rbac.Rule{
|
|
Resource: res.Value,
|
|
Operation: op.Value,
|
|
Access: acc,
|
|
},
|
|
ResourceType: rbac.RuleResourceType,
|
|
References: envoyx.MergeRefs(
|
|
map[string]envoyx.Ref{"RoleID": {
|
|
// Providing resource type as plain text to reduce cross component references
|
|
ResourceType: "corteza::system:role",
|
|
Identifiers: envoyx.MakeIdentifiers(role.Value),
|
|
}},
|
|
envoyx.SplitResourceIdentifier(res.Value),
|
|
),
|
|
})
|
|
return nil
|
|
})
|
|
})
|
|
})
|
|
}
|
|
|
|
// // // // // // // // // // // // // // // // // // // // // // // // //
|
|
// i18n unmarshal logic
|
|
// // // // // // // // // // // // // // // // // // // // // // // // //
|
|
|
|
func unmarshalLocaleNode(n *yaml.Node) (out envoyx.NodeSet, err error) {
|
|
err = y7s.EachMap(n, func(lang, loc *yaml.Node) error {
|
|
langTag := systemTypes.Lang{Tag: language.Make(lang.Value)}
|
|
|
|
return y7s.EachMap(loc, func(res, kv *yaml.Node) error {
|
|
return y7s.EachMap(kv, func(k, msg *yaml.Node) error {
|
|
out = append(out, &envoyx.Node{
|
|
Resource: &systemTypes.ResourceTranslation{
|
|
Resource: res.Value,
|
|
Lang: langTag,
|
|
K: k.Value,
|
|
Message: msg.Value,
|
|
},
|
|
// Providing resource type as plain text to reduce cross component references
|
|
ResourceType: "corteza::system:resource-translation",
|
|
References: envoyx.SplitResourceIdentifier(res.Value),
|
|
})
|
|
return nil
|
|
})
|
|
})
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
for _, o := range out {
|
|
for _, r := range o.References {
|
|
if r.Scope.IsEmpty() {
|
|
continue
|
|
}
|
|
o.Scope = r.Scope
|
|
break
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// // // // // // // // // // // // // // // // // // // // // // // // //
|
|
// Envoy config unmarshal logic
|
|
// // // // // // // // // // // // // // // // // // // // // // // // //
|
|
|
|
func (d *auxYamlDoc) decodeEnvoyConfig(n *yaml.Node) (out envoyx.EnvoyConfig) {
|
|
y7s.EachMap(n, func(k, v *yaml.Node) (err error) {
|
|
switch strings.ToLower(k.Value) {
|
|
case "skipif", "skip":
|
|
return y7s.DecodeScalar(v, "decode skip if", &out.SkipIf)
|
|
case "onexisting", "mergealg":
|
|
out.MergeAlg = envoyx.CastMergeAlg(v.Value)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
return
|
|
}
|
|
|
|
// // // // // // // // // // // // // // // // // // // // // // // // //
|
|
// Utilities
|
|
// // // // // // // // // // // // // // // // // // // // // // // // //
|
|
|
|
func (d YamlDecoder) getReader(ctx context.Context, p envoyx.DecodeParams) (r io.Reader, err error) {
|
|
aux, ok := p.Params[paramsKeyStream]
|
|
if ok {
|
|
r, ok = aux.(io.Reader)
|
|
if ok {
|
|
return
|
|
}
|
|
}
|
|
|
|
// @todo consider adding support for managing files from a location
|
|
err = errors.Errorf("YAML decoder expects a stream conforming to io.Reader interface")
|
|
return
|
|
}
|
|
|
|
func (d *auxYamlDoc) getDocumentContext(n *yaml.Node) (dctx documentContext, err error) {
|
|
dctx = documentContext{
|
|
references: make(map[string]string),
|
|
}
|
|
|
|
err = y7s.EachMap(n, func(k, v *yaml.Node) error {
|
|
// @todo expand when needed. The previous implementation only supported
|
|
// namespaces on the root of the document.
|
|
|
|
if y7s.IsKind(v, yaml.ScalarNode) {
|
|
dctx.references[k.Value] = v.Value
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
return
|
|
}
|