Add support for workflow import/export

This commit is contained in:
Tomaž Jerman
2021-03-19 08:48:58 +01:00
parent 9afba4a662
commit 69b1ac9d01
32 changed files with 1958 additions and 51 deletions
+1
View File
@@ -38,6 +38,7 @@ func mapUserstamps(n mapNode, us *resource.Userstamps) (mapNode, error) {
"updatedBy", us.UpdatedBy,
"deletedBy", us.DeletedBy,
"ownedBy", us.OwnedBy,
"runAs", us.RunAs,
)
}
+139
View File
@@ -0,0 +1,139 @@
package resource
import (
"fmt"
"strconv"
"github.com/cortezaproject/corteza-server/automation/types"
)
type (
AutomationWorkflow struct {
*base
Res *types.Workflow
Triggers []*AutomationTrigger
Steps []*AutomationWorkflowStep
Paths []*AutomationWorkflowPath
}
AutomationTrigger struct {
*base
Res *types.Trigger
}
AutomationWorkflowStep struct {
*base
Res *types.WorkflowStep
}
AutomationWorkflowPath struct {
*base
Res *types.WorkflowPath
ParentStep Identifiers
ChildStep Identifiers
}
)
func NewAutomationWorkflow(res *types.Workflow) *AutomationWorkflow {
r := &AutomationWorkflow{
base: &base{},
}
r.SetResourceType(AUTOMATION_WORKFLOW_RESOURCE_TYPE)
r.Res = res
r.AddIdentifier(identifiers(res.Handle, res.Meta.Name, res.ID)...)
// Initial stamps
r.SetTimestamps(MakeCUDATimestamps(&res.CreatedAt, res.UpdatedAt, res.DeletedAt, nil))
us := MakeCUDOUserstamps(res.CreatedBy, res.UpdatedBy, res.DeletedBy, res.OwnedBy)
us.RunAs = MakeUserstampFromID(res.RunAs)
r.SetUserstamps(us)
return r
}
func (r *AutomationWorkflow) AddAutomationTrigger(res *types.Trigger) *AutomationTrigger {
t := &AutomationTrigger{
base: &base{},
}
t.Res = res
// Initial stamps
t.SetTimestamps(MakeCUDATimestamps(&res.CreatedAt, res.UpdatedAt, res.DeletedAt, nil))
t.SetUserstamps(MakeCUDOUserstamps(res.CreatedBy, res.UpdatedBy, res.DeletedBy, res.OwnedBy))
if r.Triggers == nil {
r.Triggers = make([]*AutomationTrigger, 0, 2)
}
r.Triggers = append(r.Triggers, t)
return t
}
func (r *AutomationWorkflow) AddAutomationWorkflowStep(res *types.WorkflowStep) *AutomationWorkflowStep {
s := &AutomationWorkflowStep{
base: &base{},
}
s.Res = res
if r.Steps == nil {
r.Steps = make([]*AutomationWorkflowStep, 0, 100)
}
r.Steps = append(r.Steps, s)
return s
}
func (r *AutomationWorkflow) AddAutomationWorkflowPath(res *types.WorkflowPath) *AutomationWorkflowPath {
p := &AutomationWorkflowPath{
base: &base{},
}
p.Res = res
if r.Paths == nil {
r.Paths = make([]*AutomationWorkflowPath, 0, 100)
}
r.Paths = append(r.Paths, p)
return p
}
func (r *AutomationWorkflow) SysID() uint64 {
return r.Res.ID
}
func (r *AutomationWorkflow) Ref() string {
return FirstOkString(r.Res.Handle, r.Res.Meta.Name, strconv.FormatUint(r.Res.ID, 10))
}
// FindAutomationWorkflow looks for the workflow in the resource set
func FindAutomationWorkflow(rr InterfaceSet, ii Identifiers) (ns *types.Workflow) {
var wfRes *AutomationWorkflow
rr.Walk(func(r Interface) error {
wr, ok := r.(*AutomationWorkflow)
if !ok {
return nil
}
if wr.Identifiers().HasAny(ii) {
wfRes = wr
}
return nil
})
// Found it
if wfRes != nil {
return wfRes.Res
}
return nil
}
func AutomationWorkflowErrUnresolved(ii Identifiers) error {
return fmt.Errorf("automation workflow unresolved %v", ii.StringSlice())
}
+6 -1
View File
@@ -58,6 +58,7 @@ type (
UpdatedBy *Userstamp
DeletedBy *Userstamp
OwnedBy *Userstamp
RunAs *Userstamp
}
UserstampIndex map[string]*Userstamp
@@ -123,7 +124,7 @@ func (t *base) SetUserstamps(us *Userstamps) {
t.us = us
if us != nil {
uu := []*Userstamp{us.CreatedBy, us.UpdatedBy, us.DeletedBy, us.OwnedBy}
uu := []*Userstamp{us.CreatedBy, us.UpdatedBy, us.DeletedBy, us.OwnedBy, us.RunAs}
t.SetUserRefs(uu)
}
}
@@ -352,6 +353,10 @@ func MakeUserstampFromRef(ref string) *Userstamp {
return us
}
func MakeUserstampFromID(ID uint64) *Userstamp {
return MakeUserstampFromRef(strconv.FormatUint(ID, 10))
}
func (ux UserstampIndex) Add(uu ...*types.User) {
for _, u := range uu {
sID := strconv.FormatUint(u.ID, 10)
+14 -12
View File
@@ -1,6 +1,7 @@
package resource
import (
at "github.com/cortezaproject/corteza-server/automation/types"
ct "github.com/cortezaproject/corteza-server/compose/types"
st "github.com/cortezaproject/corteza-server/system/types"
)
@@ -39,18 +40,19 @@ type (
)
var (
APPLICATION_RESOURCE_TYPE = st.ApplicationRBACResource.String()
COMPOSE_CHART_RESOURCE_TYPE = ct.ChartRBACResource.String()
COMPOSE_MODULE_RESOURCE_TYPE = ct.ModuleRBACResource.String()
COMPOSE_NAMESPACE_RESOURCE_TYPE = ct.NamespaceRBACResource.String()
COMPOSE_PAGE_RESOURCE_TYPE = ct.PageRBACResource.String()
COMPOSE_RECORD_RESOURCE_TYPE = "compose:record:"
RBAC_RESOURCE_TYPE = "rbac:rule:"
ROLE_RESOURCE_TYPE = st.RoleRBACResource.String()
SETTINGS_RESOURCE_TYPE = "system:setting:"
USER_RESOURCE_TYPE = st.UserRBACResource.String()
TEMPLATE_RESOURCE_TYPE = st.TemplateRBACResource.String()
DATA_SOURCE_RESOURCE_TYPE = "data:raw:"
APPLICATION_RESOURCE_TYPE = st.ApplicationRBACResource.String()
COMPOSE_CHART_RESOURCE_TYPE = ct.ChartRBACResource.String()
COMPOSE_MODULE_RESOURCE_TYPE = ct.ModuleRBACResource.String()
COMPOSE_NAMESPACE_RESOURCE_TYPE = ct.NamespaceRBACResource.String()
COMPOSE_PAGE_RESOURCE_TYPE = ct.PageRBACResource.String()
COMPOSE_RECORD_RESOURCE_TYPE = "compose:record:"
RBAC_RESOURCE_TYPE = "rbac:rule:"
ROLE_RESOURCE_TYPE = st.RoleRBACResource.String()
SETTINGS_RESOURCE_TYPE = "system:setting:"
USER_RESOURCE_TYPE = st.UserRBACResource.String()
TEMPLATE_RESOURCE_TYPE = st.TemplateRBACResource.String()
DATA_SOURCE_RESOURCE_TYPE = "data:raw:"
AUTOMATION_WORKFLOW_RESOURCE_TYPE = at.WorkflowRBACResource.String()
)
func MakeIdentifiers(ss ...string) Identifiers {
+2
View File
@@ -14,6 +14,8 @@ type (
res *resource.Application
app *types.Application
ux *userIndex
}
)
+6 -2
View File
@@ -6,14 +6,18 @@ import (
"github.com/cortezaproject/corteza-server/system/types"
)
func newApplication(app *types.Application) *application {
func newApplication(app *types.Application, ux *userIndex) *application {
return &application{
app: app,
ux: ux,
}
}
func (app *application) MarshalEnvoy() ([]resource.Interface, error) {
rs := resource.NewApplication(app.app)
syncUserStamps(rs.Userstamps(), app.ux)
return envoy.CollectNodes(
resource.NewApplication(app.app),
rs,
)
}
+153
View File
@@ -0,0 +1,153 @@
package store
import (
"context"
"strings"
"github.com/cortezaproject/corteza-server/automation/types"
"github.com/cortezaproject/corteza-server/pkg/envoy"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/store"
)
type (
automationWorkflowFilter types.WorkflowFilter
automationStore interface {
store.AutomationWorkflows
store.AutomationTriggers
}
automationDecoder struct {
resourceID []uint64
ux *userIndex
}
)
func newAutomationDecoder(ux *userIndex) *automationDecoder {
return &automationDecoder{
resourceID: make([]uint64, 0, 200),
ux: ux,
}
}
func (d *automationDecoder) decodeWorkflows(ctx context.Context, s automationStore, ff []*automationWorkflowFilter) *auxRsp {
mm := make([]envoy.Marshaller, 0, 100)
if ff == nil {
return &auxRsp{
mm: mm,
}
}
var nn types.WorkflowSet
var fn types.WorkflowFilter
var err error
for _, f := range ff {
aux := *f
if aux.Limit == 0 {
aux.Limit = 1000
}
for {
nn, fn, err = s.SearchAutomationWorkflows(ctx, types.WorkflowFilter(aux))
if err != nil {
return &auxRsp{
err: err,
}
}
for _, n := range nn {
// Index users
err = d.ux.add(
ctx,
n.RunAs,
n.OwnedBy,
n.CreatedBy,
n.UpdatedBy,
n.DeletedBy,
)
if err != nil {
return &auxRsp{
err: err,
}
}
tt, _, err := s.SearchAutomationTriggers(ctx, types.TriggerFilter{
WorkflowID: []uint64{n.ID},
Disabled: filter.StateInclusive,
})
if err != nil {
return &auxRsp{
err: err,
}
}
for _, t := range tt {
// Index users
err = d.ux.add(
ctx,
t.OwnedBy,
t.CreatedBy,
t.UpdatedBy,
t.DeletedBy,
)
}
if err != nil {
return &auxRsp{
err: err,
}
}
mm = append(mm, newAutomationWorkflow(n, tt, d.ux))
d.resourceID = append(d.resourceID, n.ID)
}
if fn.NextPage != nil {
aux.PageCursor = fn.NextPage
} else {
break
}
}
}
return &auxRsp{
mm: mm,
}
}
func (df *DecodeFilter) automationFromResource(rr ...string) *DecodeFilter {
for _, r := range rr {
if !strings.HasPrefix(r, "automation") {
continue
}
id := ""
if strings.Count(r, ":") == 2 && !strings.HasSuffix(r, "*") {
// There is an identifier
aux := strings.Split(r, ":")
id = aux[len(aux)-1]
r = strings.Join(aux[:len(aux)-1], ":")
}
switch strings.ToLower(r) {
case "automation:workflow":
df = df.Workflows(&types.WorkflowFilter{
Query: id,
})
}
}
return df
}
// Roles adds a new RoleFilter
func (df *DecodeFilter) Workflows(f *types.WorkflowFilter) *DecodeFilter {
if df.automationWorkflow == nil {
df.automationWorkflow = make([]*automationWorkflowFilter, 0, 1)
}
df.automationWorkflow = append(df.automationWorkflow, (*automationWorkflowFilter)(f))
return df
}
+191
View File
@@ -0,0 +1,191 @@
package store
import (
"context"
"github.com/cortezaproject/corteza-server/automation/types"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/store"
)
type (
automationWorkflow struct {
cfg *EncoderConfig
res *resource.AutomationWorkflow
wf *types.Workflow
tt types.TriggerSet
ux *userIndex
}
automationWorkflowSet []*automationWorkflow
automationTrigger struct {
cfg *EncoderConfig
res *resource.AutomationTrigger
tr *types.Trigger
}
automationTriggerSet []*automationTrigger
)
// mergeAutomationWorkflows merges b into a, prioritising a
func mergeAutomationWorkflows(a, b *types.Workflow) *types.Workflow {
c := a
if c.Handle == "" {
c.Handle = b.Handle
}
if c.Meta == nil {
c.Meta = b.Meta
}
if c.Scope == nil {
c.Scope = b.Scope
}
if c.Steps == nil {
c.Steps = b.Steps
}
if c.Paths == nil {
c.Paths = b.Paths
}
if c.RunAs == 0 {
c.RunAs = b.RunAs
}
if c.OwnedBy == 0 {
c.OwnedBy = b.OwnedBy
}
if c.CreatedBy == 0 {
c.CreatedBy = b.CreatedBy
}
if c.UpdatedBy == 0 {
c.UpdatedBy = b.UpdatedBy
}
if c.DeletedBy == 0 {
c.DeletedBy = b.DeletedBy
}
if c.CreatedAt.IsZero() {
c.CreatedAt = b.CreatedAt
}
if c.UpdatedAt == nil {
c.UpdatedAt = b.UpdatedAt
}
if c.DeletedAt == nil {
c.DeletedAt = b.DeletedAt
}
return c
}
// findAutomationWorkflowRS looks for the workflow in the resources & the store
//
// Provided resources are prioritized.
func findAutomationWorkflowRS(ctx context.Context, s store.Storer, rr resource.InterfaceSet, ii resource.Identifiers) (wf *types.Workflow, err error) {
wf = resource.FindAutomationWorkflow(rr, ii)
if wf != nil {
return wf, nil
}
return findAutomationWorkflowS(ctx, s, makeGenericFilter(ii))
}
// findAutomationWorkflowS looks for the workflow in the store
func findAutomationWorkflowS(ctx context.Context, s store.Storer, gf genericFilter) (wf *types.Workflow, err error) {
if gf.id > 0 {
wf, err = store.LookupAutomationWorkflowByID(ctx, s, gf.id)
if err != nil && err != store.ErrNotFound {
return nil, err
}
if wf != nil {
return
}
}
for _, i := range gf.identifiers {
wf, err = store.LookupAutomationWorkflowByHandle(ctx, s, i)
if err == store.ErrNotFound {
var nn types.WorkflowSet
nn, _, err = store.SearchAutomationWorkflows(ctx, s, types.WorkflowFilter{
Query: i,
Paging: filter.Paging{
Limit: 2,
},
})
if len(nn) > 1 {
return nil, resourceErrIdentifierNotUnique(i)
}
if len(nn) == 1 {
wf = nn[0]
}
}
if err != nil && err != store.ErrNotFound {
return nil, err
}
if wf != nil {
return
}
}
return nil, nil
}
// // findAutomationTriggerRS looks for the trigger in the resources & the store
// //
// // Provided resources are prioritized.
// func findAutomationTriggerRS(ctx context.Context, s store.Storer, rr resource.InterfaceSet, ii resource.Identifiers) (wf *types.Trigger, err error) {
// wf = resource.FindAutomationTrigger(rr, ii)
// if wf != nil {
// return wf, nil
// }
// return findAutomationTriggerS(ctx, s, makeGenericFilter(ii))
// }
// // findAutomationTriggerS looks for the trigger in the store
// func findAutomationTriggerS(ctx context.Context, s store.Storer, gf genericFilter) (wf *types.Trigger, err error) {
// if gf.id > 0 {
// wf, err = store.LookupAutomationTriggerByID(ctx, s, gf.id)
// if err != nil && err != store.ErrNotFound {
// return nil, err
// }
// if wf != nil {
// return
// }
// }
// for _, i := range gf.identifiers {
// store.SearchAutomationTriggers()
// nn, _, err := store.SearchAutomationTriggers(ctx, s, types.TriggerFilter{
// Query: i,
// Paging: filter.Paging{
// Limit: 2,
// },
// })
// if len(nn) > 1 {
// return nil, resourceErrIdentifierNotUnique(i)
// }
// if len(nn) == 1 {
// wf = nn[0]
// }
// if err != nil && err != store.ErrNotFound {
// return nil, err
// }
// if wf != nil {
// return
// }
// }
// return nil, nil
// }
@@ -0,0 +1,236 @@
package store
import (
"context"
"github.com/cortezaproject/corteza-server/automation/types"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
"github.com/cortezaproject/corteza-server/pkg/filter"
"github.com/cortezaproject/corteza-server/store"
)
func newAutomationWorkflowFromResource(res *resource.AutomationWorkflow, cfg *EncoderConfig) resourceState {
return &automationWorkflow{
cfg: mergeConfig(cfg, res.Config()),
res: res,
}
}
// @todo all of the deps and stuff and things
// Prepare prepares the automationWorkflow to be encoded
//
// Any validation, additional constraining should be performed here.
func (n *automationWorkflow) Prepare(ctx context.Context, pl *payload) (err error) {
err = n.prepareWorkflows(ctx, pl)
if err != nil {
return err
}
return n.prepareTriggers(ctx, pl)
}
func (n *automationWorkflow) prepareWorkflows(ctx context.Context, pl *payload) (err error) {
// Try to get the original workflow
n.wf, err = findAutomationWorkflowS(ctx, pl.s, makeGenericFilter(n.res.Identifiers()))
if err != nil {
return err
}
if n.wf != nil {
n.res.Res.ID = n.wf.ID
}
return nil
}
func (n *automationWorkflow) prepareTriggers(ctx context.Context, pl *payload) (err error) {
if n.wf == nil || n.wf.ID == 0 {
return nil
}
// Try to find any related triggers for this workflow
tt, _, err := store.SearchAutomationTriggers(ctx, pl.s, types.TriggerFilter{
WorkflowID: []uint64{n.wf.ID},
Disabled: filter.StateInclusive,
})
if err != nil {
return err
}
n.tt = tt
return nil
}
// Encode encodes the automationWorkflow to the store
//
// Encode is allowed to do some data manipulation, but no resource constraints
// should be changed.
func (n *automationWorkflow) Encode(ctx context.Context, pl *payload) (err error) {
err = n.encodeWorkflow(ctx, pl)
if err != nil {
return err
}
return n.encodeTriggers(ctx, pl)
}
func (n *automationWorkflow) encodeWorkflow(ctx context.Context, pl *payload) (err error) {
res := n.res.Res
exists := n.wf != nil && n.wf.ID > 0
// Determine the ID
if res.ID <= 0 && exists {
res.ID = n.wf.ID
}
if res.ID <= 0 {
res.ID = NextID()
}
// Sys users
us, err := resolveUserstamps(ctx, pl.s, pl.state.ParentResources, n.res.Userstamps())
if err != nil {
return err
}
ts := n.res.Timestamps()
if ts != nil {
if ts.CreatedAt != nil {
res.CreatedAt = *ts.CreatedAt.T
} else {
res.CreatedAt = *now()
}
if ts.UpdatedAt != nil {
res.UpdatedAt = ts.UpdatedAt.T
}
if ts.DeletedAt != nil {
res.DeletedAt = ts.DeletedAt.T
}
}
if us != nil {
if us.OwnedBy != nil {
res.OwnedBy = us.OwnedBy.UserID
}
if us.CreatedBy != nil {
res.CreatedBy = us.CreatedBy.UserID
}
if us.UpdatedBy != nil {
res.UpdatedBy = us.UpdatedBy.UserID
}
if us.DeletedBy != nil {
res.DeletedBy = us.DeletedBy.UserID
}
if us.RunAs != nil {
res.RunAs = us.RunAs.UserID
}
}
res.Steps = make(types.WorkflowStepSet, 0, 100)
for _, sres := range n.res.Steps {
res.Steps = append(res.Steps, sres.Res)
}
res.Paths = make(types.WorkflowPathSet, 0, 100)
for _, pres := range n.res.Paths {
p := pres.Res
res.Paths = append(res.Paths, p)
}
// Evaluate the resource skip expression
// @todo expand available parameters; similar implementation to automation/types/record@Dict
if skip, err := basicSkipEval(ctx, n.cfg, !exists); err != nil {
return err
} else if skip {
return nil
}
// Create a fresh workflow
if !exists {
return store.CreateAutomationWorkflow(ctx, pl.s, res)
}
// Update existing workflow
switch n.cfg.OnExisting {
case resource.Skip:
return nil
case resource.MergeLeft:
res = mergeAutomationWorkflows(n.wf, res)
case resource.MergeRight:
res = mergeAutomationWorkflows(res, n.wf)
}
err = store.UpdateAutomationWorkflow(ctx, pl.s, res)
if err != nil {
return err
}
n.res.Res = res
return nil
}
func (n *automationWorkflow) encodeTriggers(ctx context.Context, pl *payload) (err error) {
exists := len(n.tt) > 0
rr := make([]*types.Trigger, 0, len(n.res.Triggers))
for _, tr := range n.res.Triggers {
res := tr.Res
res.WorkflowID = n.res.Res.ID
res.ID = NextID()
// Sys users
us, err := resolveUserstamps(ctx, pl.s, pl.state.ParentResources, tr.Userstamps())
if err != nil {
return err
}
ts := tr.Timestamps()
if ts != nil {
if ts.CreatedAt != nil {
res.CreatedAt = *ts.CreatedAt.T
} else {
res.CreatedAt = *now()
}
if ts.UpdatedAt != nil {
res.UpdatedAt = ts.UpdatedAt.T
}
if ts.DeletedAt != nil {
res.DeletedAt = ts.DeletedAt.T
}
}
if us != nil {
if us.OwnedBy != nil {
res.OwnedBy = us.OwnedBy.UserID
}
if us.CreatedBy != nil {
res.CreatedBy = us.CreatedBy.UserID
}
if us.UpdatedBy != nil {
res.UpdatedBy = us.UpdatedBy.UserID
}
if us.DeletedBy != nil {
res.DeletedBy = us.DeletedBy.UserID
}
}
rr = append(rr, res)
}
// Create a fresh workflow
if !exists {
return store.CreateAutomationTrigger(ctx, pl.s, rr...)
}
// If these triggers already exist and we wish to modify them,
// remove the old ones and create new ones
switch n.cfg.OnExisting {
case resource.Skip,
resource.MergeLeft:
return nil
}
err = store.DeleteAutomationTrigger(ctx, pl.s, n.tt...)
return store.CreateAutomationTrigger(ctx, pl.s, rr...)
}
@@ -0,0 +1,39 @@
package store
import (
"github.com/cortezaproject/corteza-server/automation/types"
"github.com/cortezaproject/corteza-server/pkg/envoy"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
)
func newAutomationWorkflow(wf *types.Workflow, tt types.TriggerSet, ux *userIndex) *automationWorkflow {
return &automationWorkflow{
wf: wf,
tt: tt,
ux: ux,
}
}
// MarshalEnvoy converts the automation workflow struct to a resource
func (awf *automationWorkflow) MarshalEnvoy() ([]resource.Interface, error) {
rs := resource.NewAutomationWorkflow(awf.wf)
syncUserStamps(rs.Userstamps(), awf.ux)
for _, t := range awf.tt {
rt := rs.AddAutomationTrigger(t)
syncUserStamps(rt.Userstamps(), awf.ux)
}
for _, s := range awf.wf.Steps {
rs.AddAutomationWorkflowStep(s)
}
for _, p := range awf.wf.Paths {
rs.AddAutomationWorkflowPath(p)
}
return envoy.CollectNodes(
rs,
)
}
+71 -2
View File
@@ -6,12 +6,16 @@ import (
"github.com/cortezaproject/corteza-server/pkg/envoy"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
"github.com/cortezaproject/corteza-server/store"
"github.com/cortezaproject/corteza-server/system/types"
)
type (
decoder struct{}
decoder struct {
ux *userIndex
}
DecodeFilter struct {
// Compose stuff
composeNamespace []*composeNamespaceFilter
composeModule []*composeModuleFilter
@@ -26,6 +30,9 @@ type (
applications []*applicationFilter
settings []*settingFilter
rbac []*rbacFilter
// Automation stuff
automationWorkflow []*automationWorkflowFilter
}
auxMarshaller []envoy.Marshaller
@@ -33,15 +40,30 @@ type (
mm []envoy.Marshaller
err error
}
// We'll use the userIndex to hold required users
userIndex struct {
users map[uint64]*types.User
s store.Users
}
)
func Decoder() *decoder {
return &decoder{}
}
func NewDecodeFilter() *DecodeFilter {
return &DecodeFilter{}
}
func (df *DecodeFilter) FromResource(rr ...string) *DecodeFilter {
df = df.systemFromResource(rr...)
df = df.automationFromResource(rr...)
// @todo others...
return df
}
func (aum auxMarshaller) MarshalEnvoy() ([]resource.Interface, error) {
ii := make([]resource.Interface, 0, len(aum))
for _, m := range aum {
@@ -59,6 +81,13 @@ func (aum auxMarshaller) MarshalEnvoy() ([]resource.Interface, error) {
func (d *decoder) Decode(ctx context.Context, s store.Storer, f *DecodeFilter) ([]resource.Interface, error) {
mm := make(auxMarshaller, 0, 100)
if d.ux == nil {
d.ux = &userIndex{
users: make(map[uint64]*types.User),
}
}
d.ux.s = s
pof := func(rr ...*auxRsp) (auxMarshaller, error) {
mm := make(auxMarshaller, 0, 200)
@@ -77,7 +106,8 @@ func (d *decoder) Decode(ctx context.Context, s store.Storer, f *DecodeFilter) (
}
compose := newComposeDecoder()
system := newSystemDecoder()
system := newSystemDecoder(d.ux)
automation := newAutomationDecoder(d.ux)
mm, err := pof(
compose.decodeComposeNamespace(ctx, s, f.composeNamespace),
@@ -91,6 +121,8 @@ func (d *decoder) Decode(ctx context.Context, s store.Storer, f *DecodeFilter) (
system.decodeTemplates(ctx, s, f.templates),
system.decodeApplications(ctx, s, f.applications),
system.decodeSettings(ctx, s, f.settings),
automation.decodeWorkflows(ctx, s, f.automationWorkflow),
)
if err != nil {
return nil, err
@@ -98,6 +130,7 @@ func (d *decoder) Decode(ctx context.Context, s store.Storer, f *DecodeFilter) (
f.allowRbacResource(compose.resourceID...)
f.allowRbacResource(system.resourceID...)
f.allowRbacResource(automation.resourceID...)
rr, err := pof(
system.decodeRbac(ctx, s, f.rbac),
)
@@ -107,3 +140,39 @@ func (d *decoder) Decode(ctx context.Context, s store.Storer, f *DecodeFilter) (
return append(rr, mm...).MarshalEnvoy()
}
func (ux *userIndex) add(ctx context.Context, uu ...uint64) error {
// List of filtered users
filtered := make([]uint64, 0, len(uu))
for _, u := range uu {
// not defined, we don't need
if u == 0 {
continue
}
// we have, no need
if ux.users[u] != nil {
continue
}
filtered = append(filtered, u)
}
if len(filtered) == 0 {
return nil
}
users, _, err := store.SearchUsers(ctx, ux.s, types.UserFilter{
UserID: filtered,
})
if err != nil {
return err
}
for _, u := range users {
ux.users[u.ID] = u
}
return nil
}
+2
View File
@@ -134,6 +134,8 @@ func (se *storeEncoder) Prepare(ctx context.Context, ee ...*envoy.ResourceState)
err = f(NewUserFromResource(res, se.cfg), ers)
case *resource.Template:
err = f(NewTemplateFromResource(res, se.cfg), ers)
case *resource.AutomationWorkflow:
err = f(newAutomationWorkflowFromResource(res, se.cfg), ers)
case *resource.Role:
err = f(NewRoleFromResource(res, se.cfg), ers)
case *resource.Application:
+2
View File
@@ -11,5 +11,7 @@ type (
res *resource.Setting
st *types.SettingValue
ux *userIndex
}
)
+7 -2
View File
@@ -6,14 +6,19 @@ import (
"github.com/cortezaproject/corteza-server/system/types"
)
func newSetting(res *types.SettingValue) *setting {
func newSetting(res *types.SettingValue, ux *userIndex) *setting {
return &setting{
st: res,
ux: ux,
}
}
func (st *setting) MarshalEnvoy() ([]resource.Interface, error) {
rs := resource.NewSetting(st.st)
syncUserStamps(rs.Userstamps(), st.ux)
return envoy.CollectNodes(
resource.NewSetting(st.st),
rs,
)
}
+57 -3
View File
@@ -2,6 +2,7 @@ package store
import (
"context"
"strings"
"github.com/cortezaproject/corteza-server/pkg/envoy"
"github.com/cortezaproject/corteza-server/pkg/rbac"
@@ -34,12 +35,14 @@ type (
systemDecoder struct {
resourceID []uint64
ux *userIndex
}
)
func newSystemDecoder() *systemDecoder {
func newSystemDecoder(ux *userIndex) *systemDecoder {
return &systemDecoder{
resourceID: make([]uint64, 0, 200),
ux: ux,
}
}
@@ -206,7 +209,13 @@ func (d *systemDecoder) decodeApplications(ctx context.Context, s systemStore, f
}
for _, n := range nn {
mm = append(mm, newApplication(n))
// Index users
err = d.ux.add(
ctx,
n.OwnerID,
)
mm = append(mm, newApplication(n, d.ux))
d.resourceID = append(d.resourceID, n.ID)
}
@@ -245,7 +254,14 @@ func (d *systemDecoder) decodeSettings(ctx context.Context, s systemStore, ff []
}
for _, n := range nn {
mm = append(mm, newSetting(n))
// Index users
err = d.ux.add(
ctx,
n.OwnedBy,
n.UpdatedBy,
)
mm = append(mm, newSetting(n, d.ux))
}
// mm = append(mm, NewSettings(nn))
@@ -306,6 +322,44 @@ func (d *systemDecoder) decodeRbac(ctx context.Context, s systemStore, ff []*rba
}
}
func (df *DecodeFilter) systemFromResource(rr ...string) *DecodeFilter {
for _, r := range rr {
if !strings.HasPrefix(r, "system") {
continue
}
id := ""
if strings.Count(r, ":") == 2 && !strings.HasSuffix(r, "*") {
// There is an identifier
aux := strings.Split(r, ":")
id = aux[len(aux)-1]
r = strings.Join(aux[:len(aux)-1], ":")
}
switch strings.ToLower(r) {
case "system:rols":
df = df.Roles(&types.RoleFilter{
Query: id,
})
case "system:uses":
df = df.Users(&types.UserFilter{
Query: id,
})
case "system:applicatios":
df = df.Applications(&types.ApplicationFilter{
Query: id,
})
case "system:settins":
df = df.Settings(&types.SettingsFilter{})
case "system:rbac":
df = df.Rbac(&rbac.RuleFilter{})
}
}
return df
}
// Roles adds a new RoleFilter
func (df *DecodeFilter) Roles(f *types.RoleFilter) *DecodeFilter {
if df.roles == nil {
+19
View File
@@ -104,6 +104,7 @@ func resolveUserstamps(ctx context.Context, s store.Storer, rr []resource.Interf
us.CreatedBy, err = fetch(us.CreatedBy)
us.UpdatedBy, err = fetch(us.UpdatedBy)
us.DeletedBy, err = fetch(us.DeletedBy)
us.RunAs, err = fetch(us.RunAs)
us.OwnedBy, err = fetch(us.OwnedBy)
if err != nil {
@@ -113,6 +114,24 @@ func resolveUserstamps(ctx context.Context, s store.Storer, rr []resource.Interf
return us, nil
}
func syncUserStamps(us *resource.Userstamps, ux *userIndex) {
if us.CreatedBy != nil && us.CreatedBy.UserID > 0 {
us.CreatedBy.U = ux.users[us.CreatedBy.UserID]
}
if us.UpdatedBy != nil && us.UpdatedBy.UserID > 0 {
us.UpdatedBy.U = ux.users[us.UpdatedBy.UserID]
}
if us.DeletedBy != nil && us.DeletedBy.UserID > 0 {
us.DeletedBy.U = ux.users[us.DeletedBy.UserID]
}
if us.OwnedBy != nil && us.OwnedBy.UserID > 0 {
us.OwnedBy.U = ux.users[us.OwnedBy.UserID]
}
if us.RunAs != nil && us.RunAs.UserID > 0 {
us.RunAs.U = ux.users[us.RunAs.UserID]
}
}
func resolveUserRefs(ctx context.Context, s store.Storer, pr []resource.Interface, refs resource.RefSet, dst map[string]uint64) (err error) {
for _, uRef := range refs {
u := resource.FindUser(pr, uRef.Identifiers)
+64
View File
@@ -0,0 +1,64 @@
package yaml
import (
"reflect"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
"github.com/cortezaproject/corteza-server/pkg/y7s"
"gopkg.in/yaml.v3"
)
type (
automation struct {
Workflows automationWorkflowSet
EncoderConfig *EncoderConfig `yaml:"-"`
}
)
func (c *automation) MarshalYAML() (interface{}, error) {
cn, _ := makeMap()
var err error
if len(c.Workflows) > 0 {
c.Workflows.ConfigureEncoder(c.EncoderConfig)
cn, err = encodeResource(cn, "workflows", c.Workflows, c.EncoderConfig.MappedOutput, "handle")
if err != nil {
return nil, err
}
}
return cn, nil
}
func (a *automation) UnmarshalYAML(n *yaml.Node) error {
var err error
return y7s.EachMap(n, func(k, v *yaml.Node) error {
switch k.Value {
case "workflows":
err = v.Decode(&a.Workflows)
if err != nil {
return err
}
}
return nil
})
}
func (a automation) MarshalEnvoy() ([]resource.Interface, error) {
nn := make([]resource.Interface, 0, 100)
rf := reflect.ValueOf(a)
for i := 0; i < rf.NumField(); i++ {
if mr, ok := rf.Field(i).Interface().(EnvoyMarshler); ok {
tmp, err := mr.MarshalEnvoy()
if err != nil {
return nil, err
}
nn = append(nn, tmp...)
}
}
return nn, nil
}
+63
View File
@@ -0,0 +1,63 @@
package yaml
import (
"github.com/cortezaproject/corteza-server/automation/types"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
)
type (
automationWorkflow struct {
res *types.Workflow
triggers automationTriggerSet
steps automationWorkflowStepSet
paths automationWorkflowPathSet
ts *resource.Timestamps
us *resource.Userstamps
envoyConfig *resource.EnvoyConfig
encoderConfig *EncoderConfig
rbac rbacRuleSet
}
automationWorkflowSet []*automationWorkflow
automationTrigger struct {
res *types.Trigger
ts *resource.Timestamps
us *resource.Userstamps
envoyConfig *resource.EnvoyConfig
encoderConfig *EncoderConfig
}
automationTriggerSet []*automationTrigger
automationWorkflowStep struct {
res *types.WorkflowStep
ts *resource.Timestamps
us *resource.Userstamps
envoyConfig *resource.EnvoyConfig
encoderConfig *EncoderConfig
}
automationWorkflowStepSet []*automationWorkflowStep
automationWorkflowPath struct {
res *types.WorkflowPath
ts *resource.Timestamps
us *resource.Userstamps
envoyConfig *resource.EnvoyConfig
encoderConfig *EncoderConfig
}
automationWorkflowPathSet []*automationWorkflowPath
)
func (nn automationWorkflowSet) ConfigureEncoder(cfg *EncoderConfig) {
for _, n := range nn {
n.encoderConfig = cfg
}
}
@@ -0,0 +1,155 @@
package yaml
import (
"context"
"github.com/cortezaproject/corteza-server/pkg/envoy"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
"github.com/cortezaproject/corteza-server/pkg/envoy/util"
)
func automationWorkflowFromResource(r *resource.AutomationWorkflow, cfg *EncoderConfig) *automationWorkflow {
tt := make(automationTriggerSet, len(r.Triggers))
for i, t := range r.Triggers {
tt[i] = &automationTrigger{
res: t.Res,
encoderConfig: cfg,
}
}
ss := make(automationWorkflowStepSet, len(r.Steps))
for i, s := range r.Steps {
ss[i] = &automationWorkflowStep{
res: s.Res,
encoderConfig: cfg,
}
}
pp := make(automationWorkflowPathSet, len(r.Paths))
for i, p := range r.Paths {
pp[i] = &automationWorkflowPath{
res: p.Res,
encoderConfig: cfg,
}
}
return &automationWorkflow{
res: r.Res,
triggers: tt,
steps: ss,
paths: pp,
encoderConfig: cfg,
}
}
// Prepare prepares the automationWorkflow to be encoded
//
// Any validation, additional constraining should be performed here.
func (n *automationWorkflow) Prepare(ctx context.Context, state *envoy.ResourceState) (err error) {
wf, ok := state.Res.(*resource.AutomationWorkflow)
if !ok {
return encoderErrInvalidResource(resource.AUTOMATION_WORKFLOW_RESOURCE_TYPE, state.Res.ResourceType())
}
n.res = wf.Res
n.us = wf.Userstamps()
return nil
}
// Encode encodes the automationWorkflow to the document
//
// Encode is allowed to do some data manipulation, but no resource constraints
// should be changed.
func (n *automationWorkflow) Encode(ctx context.Context, doc *Document, state *envoy.ResourceState) (err error) {
if n.res.ID <= 0 {
n.res.ID = util.NextID()
}
n.ts, err = resource.MakeCUDATimestamps(&n.res.CreatedAt, n.res.UpdatedAt, n.res.DeletedAt, nil).
Model(n.encoderConfig.TimeLayout, n.encoderConfig.Timezone)
if err != nil {
return err
}
n.us, err = resolveUserstamps(state.ParentResources, n.us)
if err != nil {
return err
}
// @todo skip eval?
doc.AddAutomationWorkflow(n)
return err
}
func (wf *automationWorkflow) MarshalYAML() (interface{}, error) {
var err error
nn, err := makeMap(
"handle", wf.res.Handle,
"meta", wf.res.Meta,
"enabled", wf.res.Enabled,
"trace", wf.res.Trace,
"keepSessions", wf.res.KeepSessions,
"scope", wf.res.Scope,
"triggers", wf.triggers,
"steps", wf.res.Steps,
"paths", wf.res.Paths,
// "issues", wf.res.Issues,
"labels", wf.res.Labels,
)
if err != nil {
return nil, err
}
nn, err = mapTimestamps(nn, wf.ts)
if err != nil {
return nil, err
}
nn, err = mapUserstamps(nn, wf.us)
if err != nil {
return nil, err
}
return nn, nil
}
func (t *automationTrigger) MarshalYAML() (interface{}, error) {
var err error
nn, err := makeMap(
"resourceType", t.res.ResourceType,
"eventType", t.res.EventType,
"constraints", t.res.Constraints,
"enabled", t.res.Enabled,
"workflowID", t.res.WorkflowID,
"stepID", t.res.StepID,
"input", t.res.Input,
"meta", t.res.Meta,
"labels", t.res.Labels,
)
if err != nil {
return nil, err
}
nn, err = mapTimestamps(nn, t.ts)
if err != nil {
return nil, err
}
nn, err = mapUserstamps(nn, t.us)
if err != nil {
return nil, err
}
return nn, nil
}
@@ -0,0 +1,39 @@
package yaml
import (
"testing"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)
func TestAutomationWorkflow_UnmarshalYAML(t *testing.T) {
var (
parseString = func(src string) (*automationWorkflow, error) {
w := &automationWorkflow{}
return w, yaml.Unmarshal([]byte(src), w)
}
)
t.Run("empty", func(t *testing.T) {
req := require.New(t)
w, err := parseString(``)
req.NoError(err)
req.NotNil(w)
req.Nil(w.res)
})
t.Run("workflow 1", func(t *testing.T) {
req := require.New(t)
doc, err := parseDocument("workflow_1")
req.NoError(err)
req.NotNil(doc)
req.Len(doc.automation.Workflows, 1)
req.NotNil(doc.automation.Workflows[0])
req.Len(doc.automation.Workflows[0].triggers, 1)
req.Len(doc.automation.Workflows[0].steps, 1)
req.Len(doc.automation.Workflows[0].paths, 1)
})
}
@@ -0,0 +1,289 @@
package yaml
import (
"github.com/cortezaproject/corteza-server/automation/types"
"github.com/cortezaproject/corteza-server/pkg/envoy"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
"github.com/cortezaproject/corteza-server/pkg/y7s"
"gopkg.in/yaml.v3"
)
func (wset *automationWorkflowSet) UnmarshalYAML(n *yaml.Node) error {
return y7s.Each(n, func(k, v *yaml.Node) (err error) {
var (
wrap = &automationWorkflow{}
)
if v == nil {
return y7s.NodeErr(n, "malformed automation workflow definition")
}
if err = v.Decode(&wrap); err != nil {
return
}
if err = decodeRef(k, "automation workflow handle", &wrap.res.Handle); err != nil {
return y7s.NodeErr(n, "Automation workflow reference must be a valid handle")
}
if wrap.res.Meta.Name == "" {
// if name is not set, use handle
wrap.res.Meta.Name = wrap.res.Handle
}
*wset = append(*wset, wrap)
return
})
}
func (wrap *automationWorkflow) UnmarshalYAML(n *yaml.Node) (err error) {
if wrap.res == nil {
wrap.rbac = make(rbacRuleSet, 0, 10)
wrap.res = &types.Workflow{}
}
if wrap.rbac, err = decodeRbac(n); err != nil {
return
}
if wrap.envoyConfig, err = decodeEnvoyConfig(n); err != nil {
return
}
if wrap.ts, err = decodeTimestamps(n); err != nil {
return
}
if wrap.us, err = decodeUserstamps(n); err != nil {
return
}
return y7s.EachMap(n, func(k, v *yaml.Node) (err error) {
switch k.Value {
case "handle":
return y7s.DecodeScalar(v, "workflow handle", &wrap.res.Handle)
case "meta":
return v.Decode(&wrap.res.Meta)
case "enabled":
return y7s.DecodeScalar(v, "workflow enabled", &wrap.res.Enabled)
case "trace":
return y7s.DecodeScalar(v, "workflow trace", &wrap.res.Trace)
case "keepSessions":
return y7s.DecodeScalar(v, "workflow keepSessions", &wrap.res.KeepSessions)
case "scope":
err = v.Decode(&wrap.res.Scope)
if err != nil {
return err
}
case "triggers":
wrap.triggers = make(automationTriggerSet, 0, 100)
err = v.Decode(&wrap.triggers)
if err != nil {
return err
}
case "steps":
wrap.steps = make(automationWorkflowStepSet, 0, 100)
err = v.Decode(&wrap.steps)
if err != nil {
return err
}
case "paths":
wrap.paths = make(automationWorkflowPathSet, 0, 100)
err = v.Decode(&wrap.paths)
if err != nil {
return err
}
}
return nil
})
}
func (wrap *automationTrigger) UnmarshalYAML(n *yaml.Node) (err error) {
if wrap.res == nil {
wrap.res = &types.Trigger{}
}
if wrap.envoyConfig, err = decodeEnvoyConfig(n); err != nil {
return
}
if wrap.ts, err = decodeTimestamps(n); err != nil {
return
}
if wrap.us, err = decodeUserstamps(n); err != nil {
return
}
return y7s.EachMap(n, func(k, v *yaml.Node) (err error) {
switch k.Value {
case "enabled":
return y7s.DecodeScalar(v, "trigger enabled", &wrap.res.Enabled)
case "stepID":
return y7s.DecodeScalar(v, "trigger step", &wrap.res.StepID)
case "resourceType":
return y7s.DecodeScalar(v, "trigger resourceType", &wrap.res.ResourceType)
case "eventType":
return y7s.DecodeScalar(v, "trigger eventType", &wrap.res.EventType)
case "constraints":
return v.Decode(&wrap.res.Constraints)
case "input":
err = v.Decode(&wrap.res.Input)
if err != nil {
return err
}
case "meta":
return v.Decode(&wrap.res.Meta)
}
return nil
})
}
func (wrap *automationWorkflowStep) UnmarshalYAML(n *yaml.Node) (err error) {
if wrap.res == nil {
wrap.res = &types.WorkflowStep{}
}
if wrap.envoyConfig, err = decodeEnvoyConfig(n); err != nil {
return
}
return y7s.EachMap(n, func(k, v *yaml.Node) (err error) {
switch k.Value {
case "stepID":
return y7s.DecodeScalar(v, "trigger step", &wrap.res.ID)
case "kind":
return y7s.DecodeScalar(v, "step kind", &wrap.res.Kind)
case "ref":
return y7s.DecodeScalar(v, "step ref", &wrap.res.Ref)
case "arguments":
wrap.res.Arguments, err = unmarshalExprSet(v)
return err
case "results":
wrap.res.Results, err = unmarshalExprSet(v)
return err
case "meta":
return v.Decode(&wrap.res.Meta)
}
return nil
})
}
func (wrap *automationWorkflowPath) UnmarshalYAML(n *yaml.Node) (err error) {
if wrap.res == nil {
wrap.res = &types.WorkflowPath{}
}
if wrap.envoyConfig, err = decodeEnvoyConfig(n); err != nil {
return
}
return y7s.EachMap(n, func(k, v *yaml.Node) (err error) {
switch k.Value {
case "expr":
return y7s.DecodeScalar(v, "path expr", &wrap.res.Expr)
case "parentID":
return y7s.DecodeScalar(v, "parent ref", &wrap.res.ParentID)
case "childID":
return y7s.DecodeScalar(v, "child ref", &wrap.res.ChildID)
case "meta":
return v.Decode(&wrap.res.Meta)
}
return nil
})
}
func unmarshalExprSet(n *yaml.Node) ([]*types.Expr, error) {
ee := make([]*types.Expr, 0, 10)
err := y7s.EachSeq(n, func(v *yaml.Node) (err error) {
wrap, err := unmarshalExpr(v)
ee = append(ee, wrap)
return err
})
return ee, err
}
func unmarshalExpr(n *yaml.Node) (*types.Expr, error) {
wrap := &types.Expr{}
err := y7s.EachMap(n, func(k, v *yaml.Node) (err error) {
switch k.Value {
case "target":
return y7s.DecodeScalar(v, "expression target", &wrap.Target)
case "source":
return y7s.DecodeScalar(v, "expression source", &wrap.Source)
case "expr":
return y7s.DecodeScalar(v, "expression expr", &wrap.Expr)
case "value":
return y7s.DecodeScalar(v, "expression value", &wrap.Value)
case "type":
return y7s.DecodeScalar(v, "expression type", &wrap.Type)
case "tests":
tt := make(types.TestSet, 0, 2)
err = v.Decode(&tt)
if err != nil {
return err
}
wrap.Tests = tt
return nil
}
return nil
})
return wrap, err
}
func (wset automationWorkflowSet) MarshalEnvoy() ([]resource.Interface, error) {
// namespace usually have bunch of sub-resources defined
nn := make([]resource.Interface, 0, len(wset)*10)
for _, res := range wset {
if tmp, err := res.MarshalEnvoy(); err != nil {
return nil, err
} else {
nn = append(nn, tmp...)
}
}
return nn, nil
}
func (wrap automationWorkflow) MarshalEnvoy() ([]resource.Interface, error) {
rs := resource.NewAutomationWorkflow(wrap.res)
rs.SetTimestamps(wrap.ts)
rs.SetUserstamps(wrap.us)
rs.SetConfig(wrap.envoyConfig)
for _, t := range wrap.triggers {
trs := rs.AddAutomationTrigger(t.res)
trs.SetTimestamps(t.ts)
trs.SetUserstamps(t.us)
}
for _, s := range wrap.steps {
rs.AddAutomationWorkflowStep(s.res)
}
for _, p := range wrap.paths {
rs.AddAutomationWorkflowPath(p.res)
}
return envoy.CollectNodes(
rs,
wrap.rbac.bindResource(rs),
)
}
+8 -1
View File
@@ -149,9 +149,15 @@ func resolveUserstamps(rr []resource.Interface, us *resource.Userstamps) (*resou
}
fetch := func(us *resource.Userstamp) (*resource.Userstamp, error) {
if us == nil {
if us == nil || us.UserID == 0 {
return nil, nil
}
// This one can be considered as valid
if us.Ref != "" && us.UserID > 0 && us.U != nil {
return us, nil
}
ii := resource.MakeIdentifiers()
if us.UserID > 0 {
@@ -184,6 +190,7 @@ func resolveUserstamps(rr []resource.Interface, us *resource.Userstamps) (*resou
us.UpdatedBy, err = fetch(us.UpdatedBy)
us.DeletedBy, err = fetch(us.DeletedBy)
us.OwnedBy, err = fetch(us.OwnedBy)
us.RunAs, err = fetch(us.RunAs)
if err != nil {
return nil, err
+32 -1
View File
@@ -12,7 +12,9 @@ import (
type (
// Document defines the supported yaml structure
Document struct {
compose *compose
compose *compose
automation *automation
roles roleSet
users userSet
templates templateSet
@@ -29,6 +31,10 @@ func (doc *Document) UnmarshalYAML(n *yaml.Node) (err error) {
return
}
if err = n.Decode(&doc.automation); err != nil {
return
}
if doc.rbac, err = decodeRbac(n); err != nil {
return
}
@@ -71,6 +77,17 @@ func (doc *Document) MarshalYAML() (interface{}, error) {
dn, _ = inlineContent(dn, cn)
}
if doc.automation != nil {
doc.automation.EncoderConfig = doc.cfg
cn, err := encodeNode(doc.automation)
if err != nil {
return nil, err
}
dn, _ = inlineContent(dn, cn)
}
if doc.roles != nil {
doc.roles.ConfigureEncoder(doc.cfg)
@@ -149,6 +166,9 @@ func (doc *Document) Decode(ctx context.Context) ([]resource.Interface, error) {
if doc.templates != nil {
mm = append(mm, doc.templates)
}
if doc.automation != nil {
mm = append(mm, doc.automation)
}
if doc.applications != nil {
mm = append(mm, doc.applications)
}
@@ -234,6 +254,17 @@ func (doc *Document) AddComposeChart(c *composeChart) {
doc.compose.Charts = append(doc.compose.Charts, c)
}
func (doc *Document) AddAutomationWorkflow(m *automationWorkflow) {
if doc.automation == nil {
doc.automation = &automation{}
}
if doc.automation.Workflows == nil {
doc.automation.Workflows = make(automationWorkflowSet, 0)
}
doc.automation.Workflows = append(doc.automation.Workflows, m)
}
// AddRole adds a new role to the document
func (doc *Document) AddRole(r *role) {
if doc.roles == nil {
+3
View File
@@ -134,6 +134,9 @@ func (ye *yamlEncoder) Prepare(ctx context.Context, ee ...*envoy.ResourceState)
case *resource.RbacRule:
err = f(rbacRuleFromResource(res, ye.cfg), e)
case *resource.AutomationWorkflow:
err = f(automationWorkflowFromResource(res, ye.cfg), e)
default:
err = ErrUnknownResource
}
+4
View File
@@ -69,6 +69,10 @@ func decodeUserstamps(n *yaml.Node) (*resource.Userstamps, error) {
"ownerid",
"owner":
us.OwnedBy, err = f(v)
case "runas",
"runasid",
"runner":
us.RunAs, err = f(v)
}
return err
})
+75
View File
@@ -0,0 +1,75 @@
workflows:
testko_wf:
enabled: true
trace: true
keepSessions: 2
meta:
name: name here
description: description here
triggers:
- enabled: true
stepID: 101
resourceType: "compose:module"
eventType: "beforeCreate"
constraints:
- name: name
op: op
values:
- values
meta:
description: description
visual: {}
ownedBy: "219521853847258224"
createdAt: "2021-02-23T09:53:41Z"
createdBy: "219521853847258224"
updatedAt: "2021-02-23T09:53:41Z"
updatedBy: "219521853847258224"
deletedAt: "2021-02-23T09:53:41Z"
deletedBy: "219521853847258224"
steps:
- stepID: 101
kind: "termination"
arguments:
- target: target
source: source
expr: expr
value: value
type: type
tests:
- expr: expr
error: error
results:
- target: target
source: source
expr: expr
value: value
type: type
tests:
- expr: expr
error: error
meta:
name: name
description: description
visual: {}
paths:
- expr: expr
parentID: 101
childID: 101
meta:
name: name
description: description
visual: {}
runAs: "219521853847258224"
ownedBy: "219521853847258224"
createdAt: "2021-02-23T09:53:41Z"
createdBy: "219521853847258224"
updatedAt: "2021-02-23T09:53:41Z"
updatedBy: "219521853847258224"
deletedAt: "2021-02-23T09:53:41Z"
deletedBy: "219521853847258224"
+1
View File
@@ -34,6 +34,7 @@ func mapUserstamps(n *yaml.Node, us *resource.Userstamps) (*yaml.Node, error) {
"updatedBy", us.UpdatedBy,
"deletedBy", us.DeletedBy,
"ownedBy", us.OwnedBy,
"runAs", us.RunAs,
)
}
+88
View File
@@ -0,0 +1,88 @@
package envoy
import (
"context"
"testing"
"github.com/cortezaproject/corteza-server/automation/types"
su "github.com/cortezaproject/corteza-server/pkg/envoy/store"
"github.com/cortezaproject/corteza-server/store"
)
func sTestAutomationWorkflow(ctx context.Context, t *testing.T, s store.Storer, pfx string) *types.Workflow {
wf := &types.Workflow{
ID: su.NextID(),
Handle: pfx + "_handle",
Meta: &types.WorkflowMeta{
Name: pfx + "_name",
Description: pfx + "_description",
},
Enabled: true,
Trace: true,
KeepSessions: 10,
Steps: types.WorkflowStepSet{
&types.WorkflowStep{
ID: 11,
Kind: "function",
},
&types.WorkflowStep{
ID: 12,
Kind: "function",
},
},
Paths: types.WorkflowPathSet{
&types.WorkflowPath{
ParentID: 11,
ChildID: 12,
Expr: "qwerty",
},
},
CreatedAt: createdAt,
UpdatedAt: &updatedAt,
}
err := store.CreateAutomationWorkflow(ctx, s, wf)
if err != nil {
t.Fatal(err)
}
return wf
}
func sTestAutomationTrigger(ctx context.Context, t *testing.T, s store.Storer, wfID uint64, pfx string) *types.Trigger {
wf := &types.Trigger{
ID: su.NextID(),
Enabled: true,
WorkflowID: wfID,
StepID: 11,
ResourceType: "testko:test:",
Constraints: types.TriggerConstraintSet{
&types.TriggerConstraint{
Name: "qwerty",
Op: "=",
Values: []string{
"a",
"b",
},
},
},
Meta: &types.TriggerMeta{
Description: pfx + "_description",
},
CreatedAt: createdAt,
UpdatedAt: &updatedAt,
}
err := store.CreateAutomationTrigger(ctx, s, wf)
if err != nil {
t.Fatal(err)
}
return wf
}
+3
View File
@@ -96,6 +96,9 @@ func truncateStore(ctx context.Context, s store.Storer, t *testing.T) {
s.TruncateApplications(ctx),
s.TruncateSettings(ctx),
s.TruncateRbacRules(ctx),
s.TruncateAutomationWorkflows(ctx),
s.TruncateAutomationTriggers(ctx),
)
if err != nil {
t.Fatal(err.Error())
+55 -20
View File
@@ -7,7 +7,8 @@ import (
"testing"
"time"
ctypes "github.com/cortezaproject/corteza-server/compose/types"
atypes "github.com/cortezaproject/corteza-server/automation/types"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/auth"
"github.com/cortezaproject/corteza-server/pkg/envoy"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
@@ -48,11 +49,45 @@ func TestStoreYaml_base(t *testing.T) {
}
cases := []*tc{
{
name: "base automation workflow",
pre: func(ctx context.Context, s store.Storer) (error, *su.DecodeFilter) {
wf := sTestAutomationWorkflow(ctx, t, s, "base")
sTestAutomationTrigger(ctx, t, s, wf.ID, "base")
df := su.NewDecodeFilter().Workflows(&atypes.WorkflowFilter{})
return nil, df
},
check: func(ctx context.Context, s store.Storer, req *require.Assertions) {
wf, err := store.LookupAutomationWorkflowByHandle(ctx, s, "base_handle")
req.NoError(err)
req.NotNil(wf)
req.Equal("base_handle", wf.Handle)
req.Equal("base_name", wf.Meta.Name)
req.Equal("base_description", wf.Meta.Description)
req.True(wf.Enabled)
req.True(wf.Trace)
req.Equal(10, wf.KeepSessions)
req.Len(wf.Steps, 2)
req.Len(wf.Paths, 1)
req.Equal(createdAt.Format(time.RFC3339), wf.CreatedAt.Format(time.RFC3339))
req.Equal(updatedAt.Format(time.RFC3339), wf.UpdatedAt.Format(time.RFC3339))
tt, _, err := store.SearchAutomationTriggers(ctx, s, atypes.TriggerFilter{
WorkflowID: []uint64{wf.ID},
})
req.NoError(err)
req.NotNil(tt)
req.Len(tt, 1)
},
},
{
name: "base namespace",
pre: func(ctx context.Context, s store.Storer) (error, *su.DecodeFilter) {
sTestComposeNamespace(ctx, t, s, "base")
df := su.NewDecodeFilter().ComposeNamespace(&ctypes.NamespaceFilter{
df := su.NewDecodeFilter().ComposeNamespace(&types.NamespaceFilter{
Slug: "base_namespace",
})
return nil, df
@@ -78,10 +113,10 @@ func TestStoreYaml_base(t *testing.T) {
sTestComposeModule(ctx, t, s, ns.ID, "base")
df := su.NewDecodeFilter().
ComposeNamespace(&ctypes.NamespaceFilter{
ComposeNamespace(&types.NamespaceFilter{
Slug: "base_namespace",
}).
ComposeModule(&ctypes.ModuleFilter{
ComposeModule(&types.ModuleFilter{
NamespaceID: ns.ID,
Handle: "base_module",
})
@@ -93,7 +128,7 @@ func TestStoreYaml_base(t *testing.T) {
mod, err := store.LookupComposeModuleByNamespaceIDHandle(ctx, s, n.ID, "base_module")
req.NoError(err)
mff, _, err := store.SearchComposeModuleFields(ctx, s, ctypes.ModuleFieldFilter{
mff, _, err := store.SearchComposeModuleFields(ctx, s, types.ModuleFieldFilter{
ModuleID: []uint64{mod.ID},
})
req.NoError(err)
@@ -139,10 +174,10 @@ func TestStoreYaml_base(t *testing.T) {
sTestComposePage(ctx, t, s, ns.ID, "base")
df := su.NewDecodeFilter().
ComposeNamespace(&ctypes.NamespaceFilter{
ComposeNamespace(&types.NamespaceFilter{
Slug: "base_namespace",
}).
ComposePage(&ctypes.PageFilter{
ComposePage(&types.PageFilter{
NamespaceID: ns.ID,
Handle: "base_page",
})
@@ -187,14 +222,14 @@ func TestStoreYaml_base(t *testing.T) {
sTestComposeChart(ctx, t, s, ns.ID, mod.ID, "base")
df := su.NewDecodeFilter().
ComposeNamespace(&ctypes.NamespaceFilter{
ComposeNamespace(&types.NamespaceFilter{
Slug: "base_namespace",
}).
ComposeModule(&ctypes.ModuleFilter{
ComposeModule(&types.ModuleFilter{
NamespaceID: ns.ID,
Handle: "base_module",
}).
ComposeChart(&ctypes.ChartFilter{
ComposeChart(&types.ChartFilter{
NamespaceID: ns.ID,
Handle: "base_chart",
})
@@ -233,17 +268,17 @@ func TestStoreYaml_base(t *testing.T) {
sTestComposeRecord(ctx, t, s, ns.ID, mod.ID, usr.ID)
df := su.NewDecodeFilter().
ComposeNamespace(&ctypes.NamespaceFilter{
ComposeNamespace(&types.NamespaceFilter{
Slug: "base_namespace",
}).
ComposeModule(&ctypes.ModuleFilter{
ComposeModule(&types.ModuleFilter{
NamespaceID: ns.ID,
Handle: "base_module",
}).
Users(&stypes.UserFilter{
Email: "base_user@test.tld",
}).
ComposeRecord(&ctypes.RecordFilter{
ComposeRecord(&types.RecordFilter{
NamespaceID: ns.ID,
ModuleID: mod.ID,
})
@@ -257,7 +292,7 @@ func TestStoreYaml_base(t *testing.T) {
usr, err := store.LookupUserByHandle(ctx, s, "base_user")
req.NoError(err)
rr, _, err := store.SearchComposeRecords(ctx, s, mod, ctypes.RecordFilter{
rr, _, err := store.SearchComposeRecords(ctx, s, mod, types.RecordFilter{
ModuleID: mod.ID,
NamespaceID: ns.ID,
})
@@ -293,12 +328,12 @@ func TestStoreYaml_base(t *testing.T) {
usr := sTestUser(ctx, t, s, "base")
recID := su.NextID()
rec := &ctypes.Record{
rec := &types.Record{
ID: recID,
NamespaceID: ns.ID,
ModuleID: mod.ID,
Values: ctypes.RecordValueSet{
Values: types.RecordValueSet{
{
RecordID: recID,
Name: "BoolTrue",
@@ -353,17 +388,17 @@ func TestStoreYaml_base(t *testing.T) {
}
df := su.NewDecodeFilter().
ComposeNamespace(&ctypes.NamespaceFilter{
ComposeNamespace(&types.NamespaceFilter{
Slug: "base_namespace",
}).
ComposeModule(&ctypes.ModuleFilter{
ComposeModule(&types.ModuleFilter{
NamespaceID: ns.ID,
Handle: "base_module",
}).
Users(&stypes.UserFilter{
Email: "base_user@test.tld",
}).
ComposeRecord(&ctypes.RecordFilter{
ComposeRecord(&types.RecordFilter{
NamespaceID: ns.ID,
ModuleID: mod.ID,
})
@@ -377,7 +412,7 @@ func TestStoreYaml_base(t *testing.T) {
usr, err := store.LookupUserByHandle(ctx, s, "base_user")
req.NoError(err)
rr, _, err := store.SearchComposeRecords(ctx, s, mod, ctypes.RecordFilter{
rr, _, err := store.SearchComposeRecords(ctx, s, mod, types.RecordFilter{
ModuleID: mod.ID,
NamespaceID: ns.ID,
})
+103
View File
@@ -0,0 +1,103 @@
workflows:
testko_wf:
enabled: true
trace: true
keepSessions: 2
meta:
name: name here
description: description here
triggers:
- enabled: true
stepID: 101
resourceType: "compose:module"
eventType: "beforeCreate"
constraints:
- name: name
op: op
values:
- values
meta:
description: description
visual:
edges:
- childID: 101
parentID: 102
meta:
description": ""
label": "test"
visual":
source: 101
target: 102
value: "test"
value: "start"
xywh: [2100, 2160, 200, 80]
createdAt: "2021-02-23T09:53:41Z"
updatedAt: "2021-02-23T09:53:41Z"
steps:
- stepID: 101
kind: "expressions"
arguments:
- target: target
source: source
expr: expr
value: value
type: type
tests:
- expr: expr
error: error
results:
- target: target
source: source
expr: expr
value: value
type: type
tests:
- expr: expr
error: error
meta:
name: name
description: description
visual:
value: "expr 1"
xywh: [2340, 2310, 200, 80]
- stepID: 102
kind: "expressions"
arguments:
- target: target
source: source
expr: expr
value: value
type: type
tests:
- expr: expr
error: error
results:
- target: target
source: source
expr: expr
value: value
type: type
tests:
- expr: expr
error: error
meta:
name: name
description: description
visual:
value: "expr 1"
xywh: [2540, 2310, 200, 80]
paths:
- expr: expr
parentID: 101
childID: 102
meta:
name: name
description: description
visual: {}
+31 -7
View File
@@ -7,7 +7,8 @@ import (
"strings"
"testing"
"github.com/cortezaproject/corteza-server/compose/types"
atypes "github.com/cortezaproject/corteza-server/automation/types"
ctypes "github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/auth"
"github.com/cortezaproject/corteza-server/pkg/envoy/resource"
su "github.com/cortezaproject/corteza-server/pkg/envoy/store"
@@ -82,7 +83,7 @@ func TestYamlStore_base(t *testing.T) {
req.NoError(err)
req.NotNil(mod)
mod.Fields, _, err = store.SearchComposeModuleFields(ctx, s, types.ModuleFieldFilter{ModuleID: []uint64{mod.ID}})
mod.Fields, _, err = store.SearchComposeModuleFields(ctx, s, ctypes.ModuleFieldFilter{ModuleID: []uint64{mod.ID}})
req.NoError(err)
req.Equal("mod1", mod.Handle)
@@ -310,6 +311,29 @@ func TestYamlStore_base(t *testing.T) {
},
},
{
name: "workflows",
file: "workflows",
check: func(req *require.Assertions) {
ww, err := store.LookupAutomationWorkflowByHandle(ctx, s, "testko_wf")
req.NoError(err)
req.NotNil(ww)
req.Equal("testko_wf", ww.Handle)
req.Equal("name here", ww.Meta.Name)
req.Equal("description here", ww.Meta.Description)
req.Len(ww.Steps, 2)
req.Len(ww.Paths, 1)
tt, _, err := store.SearchAutomationTriggers(ctx, s, atypes.TriggerFilter{
WorkflowID: []uint64{ww.ID},
})
req.NoError(err)
req.NotNil(tt)
req.Len(tt, 1)
},
},
{
name: "roles",
file: "roles",
@@ -402,7 +426,7 @@ func TestYamlStore_base(t *testing.T) {
req.NoError(err)
req.NotNil(m)
rr, _, err := store.SearchComposeRecords(ctx, s, m, types.RecordFilter{ModuleID: m.ID, NamespaceID: m.NamespaceID})
rr, _, err := store.SearchComposeRecords(ctx, s, m, ctypes.RecordFilter{ModuleID: m.ID, NamespaceID: m.NamespaceID})
req.NoError(err)
req.NotNil(rr)
req.Len(rr, 1)
@@ -439,13 +463,13 @@ func TestYamlStore_base(t *testing.T) {
req.NoError(err)
req.NotNil(mod2)
rr, _, err := store.SearchComposeRecords(ctx, s, mod1, types.RecordFilter{ModuleID: mod1.ID, NamespaceID: mod1.NamespaceID})
rr, _, err := store.SearchComposeRecords(ctx, s, mod1, ctypes.RecordFilter{ModuleID: mod1.ID, NamespaceID: mod1.NamespaceID})
req.NoError(err)
req.NotNil(rr)
req.Len(rr, 1)
req.Equal("mod1 f1 v1", rr[0].Values[0].Value)
rr, _, err = store.SearchComposeRecords(ctx, s, mod2, types.RecordFilter{ModuleID: mod2.ID, NamespaceID: mod2.NamespaceID})
rr, _, err = store.SearchComposeRecords(ctx, s, mod2, ctypes.RecordFilter{ModuleID: mod2.ID, NamespaceID: mod2.NamespaceID})
req.NoError(err)
req.NotNil(rr)
req.Len(rr, 1)
@@ -477,13 +501,13 @@ func TestYamlStore_base(t *testing.T) {
req.NoError(err)
req.NotNil(mod2)
rr1, _, err := store.SearchComposeRecords(ctx, s, mod1, types.RecordFilter{ModuleID: mod1.ID, NamespaceID: mod1.NamespaceID})
rr1, _, err := store.SearchComposeRecords(ctx, s, mod1, ctypes.RecordFilter{ModuleID: mod1.ID, NamespaceID: mod1.NamespaceID})
req.NoError(err)
req.NotNil(rr1)
req.Len(rr1, 1)
req.Equal("existing value", rr1[0].Values.FilterByName("f1")[0].Value)
rr2, _, err := store.SearchComposeRecords(ctx, s, mod2, types.RecordFilter{ModuleID: mod2.ID, NamespaceID: mod2.NamespaceID})
rr2, _, err := store.SearchComposeRecords(ctx, s, mod2, ctypes.RecordFilter{ModuleID: mod2.ID, NamespaceID: mod2.NamespaceID})
req.NoError(err)
req.NotNil(rr2)
req.Len(rr2, 1)