Add support for workflow import/export
This commit is contained in:
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -14,6 +14,8 @@ type (
|
||||
|
||||
res *resource.Application
|
||||
app *types.Application
|
||||
|
||||
ux *userIndex
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -11,5 +11,7 @@ type (
|
||||
|
||||
res *resource.Setting
|
||||
st *types.SettingValue
|
||||
|
||||
ux *userIndex
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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"
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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())
|
||||
|
||||
@@ -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
@@ -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: {}
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user