snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
This commit is contained in:
728
server/automation/service/trigger.go
Normal file
728
server/automation/service/trigger.go
Normal file
@@ -0,0 +1,728 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/automation/types"
|
||||
cmpEvent "github.com/cortezaproject/corteza/server/compose/service/event"
|
||||
"github.com/cortezaproject/corteza/server/pkg/actionlog"
|
||||
"github.com/cortezaproject/corteza/server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza/server/pkg/errors"
|
||||
"github.com/cortezaproject/corteza/server/pkg/eventbus"
|
||||
"github.com/cortezaproject/corteza/server/pkg/expr"
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
"github.com/cortezaproject/corteza/server/pkg/id"
|
||||
"github.com/cortezaproject/corteza/server/pkg/label"
|
||||
"github.com/cortezaproject/corteza/server/pkg/logger"
|
||||
"github.com/cortezaproject/corteza/server/pkg/options"
|
||||
"github.com/cortezaproject/corteza/server/pkg/wfexec"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
sysEvent "github.com/cortezaproject/corteza/server/system/service/event"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type (
|
||||
trigger struct {
|
||||
eventbus triggerEventTriggerHandler
|
||||
store store.Storer
|
||||
actionlog actionlog.Recorder
|
||||
ac triggerAccessController
|
||||
|
||||
opt options.WorkflowOpt
|
||||
|
||||
log *zap.Logger
|
||||
|
||||
// maps registered triggers (value, uintptr) to trigger ID (key, uint64)
|
||||
// this will keep track of all our trigger registrations and help us do a cleanup on
|
||||
// trigger update.
|
||||
triggers map[uint64]uintptr
|
||||
|
||||
reg map[uint64]map[uint64]uintptr
|
||||
|
||||
workflow *workflow
|
||||
session *session
|
||||
|
||||
mux *sync.RWMutex
|
||||
}
|
||||
|
||||
triggerAccessController interface {
|
||||
CanSearchTriggers(context.Context) bool
|
||||
CanManageTriggersOnWorkflow(context.Context, *types.Workflow) bool
|
||||
CanExecuteWorkflow(context.Context, *types.Workflow) bool
|
||||
}
|
||||
|
||||
triggerEventTriggerHandler interface {
|
||||
Register(h eventbus.HandlerFn, ops ...eventbus.HandlerRegOp) uintptr
|
||||
Unregister(ptrs ...uintptr)
|
||||
}
|
||||
|
||||
triggerUpdateHandler func(ctx context.Context, ns *types.Trigger) (triggerChanges, error)
|
||||
triggerChanges uint8
|
||||
|
||||
varsEncoder interface {
|
||||
EncodeVars() (*expr.Vars, error)
|
||||
}
|
||||
|
||||
varsDecoder interface {
|
||||
DecodeVars(*expr.Vars) error
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
triggerUnchanged triggerChanges = 0
|
||||
triggerChanged triggerChanges = 1
|
||||
triggerLabelsChanged triggerChanges = 2
|
||||
)
|
||||
|
||||
func Trigger(log *zap.Logger, opt options.WorkflowOpt) *trigger {
|
||||
return &trigger{
|
||||
log: log,
|
||||
opt: opt,
|
||||
eventbus: eventbus.Service(),
|
||||
actionlog: DefaultActionlog,
|
||||
store: DefaultStore,
|
||||
ac: DefaultAccessControl,
|
||||
session: DefaultSession,
|
||||
workflow: DefaultWorkflow,
|
||||
triggers: make(map[uint64]uintptr),
|
||||
reg: make(map[uint64]map[uint64]uintptr),
|
||||
mux: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *trigger) Search(ctx context.Context, filter types.TriggerFilter) (rr types.TriggerSet, f types.TriggerFilter, err error) {
|
||||
var (
|
||||
wap = &triggerActionProps{filter: &filter}
|
||||
)
|
||||
|
||||
err = func() (err error) {
|
||||
if !svc.ac.CanSearchTriggers(ctx) {
|
||||
return TriggerErrNotAllowedToSearch()
|
||||
}
|
||||
|
||||
if len(filter.Labels) > 0 {
|
||||
filter.LabeledIDs, err = label.Search(
|
||||
ctx,
|
||||
svc.store,
|
||||
types.Trigger{}.LabelResourceKind(),
|
||||
filter.Labels,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// labels specified but no labeled resources found
|
||||
if len(filter.LabeledIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if rr, f, err = store.SearchAutomationTriggers(ctx, svc.store, filter); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = label.Load(ctx, svc.store, toLabeledTriggers(rr)...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}()
|
||||
|
||||
return rr, filter, svc.recordAction(ctx, wap, TriggerActionSearch, err)
|
||||
}
|
||||
|
||||
// SearchOnManual finds first matching onManual trigger and returns it
|
||||
//
|
||||
// In case stepID is 0, first trigger is returned
|
||||
func (svc *trigger) SearchOnManual(ctx context.Context, workflowID, stepID uint64) (*types.Trigger, error) {
|
||||
tt, _, err := svc.Search(ctx, types.TriggerFilter{
|
||||
WorkflowID: id.Strings(workflowID),
|
||||
EventType: "onManual",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if stepID == 0 && len(tt) > 0 {
|
||||
return tt[0], nil
|
||||
}
|
||||
|
||||
for _, t := range tt {
|
||||
if t.StepID == stepID {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (svc *trigger) LookupByID(ctx context.Context, triggerID uint64) (res *types.Trigger, err error) {
|
||||
var (
|
||||
wap = &triggerActionProps{trigger: &types.Trigger{ID: triggerID}}
|
||||
)
|
||||
|
||||
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) error {
|
||||
if !svc.ac.CanSearchTriggers(ctx) {
|
||||
return TriggerErrNotAllowedToRead()
|
||||
}
|
||||
|
||||
if res, err = loadTrigger(ctx, s, triggerID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = label.Load(ctx, svc.store, res); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return res, svc.recordAction(ctx, wap, TriggerActionLookup, err)
|
||||
}
|
||||
|
||||
// Create adds new trigger resource and saves it into store
|
||||
// It updates service's cache
|
||||
func (svc *trigger) Create(ctx context.Context, new *types.Trigger) (res *types.Trigger, err error) {
|
||||
var (
|
||||
wap = &triggerActionProps{new: new}
|
||||
cUser = auth.GetIdentityFromContext(ctx).Identity()
|
||||
)
|
||||
|
||||
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
|
||||
var (
|
||||
wf *types.Workflow
|
||||
)
|
||||
|
||||
if wf, err = loadWorkflow(ctx, svc.store, new.WorkflowID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !svc.ac.CanManageTriggersOnWorkflow(ctx, wf) {
|
||||
return TriggerErrNotAllowedToCreate()
|
||||
}
|
||||
|
||||
res = &types.Trigger{
|
||||
ID: nextID(),
|
||||
Enabled: new.Enabled,
|
||||
WorkflowID: new.WorkflowID,
|
||||
StepID: new.StepID,
|
||||
ResourceType: new.ResourceType,
|
||||
EventType: new.EventType,
|
||||
Constraints: new.Constraints,
|
||||
Input: new.Input,
|
||||
Labels: new.Labels,
|
||||
Meta: new.Meta,
|
||||
OwnedBy: cUser,
|
||||
CreatedAt: *now(),
|
||||
CreatedBy: cUser,
|
||||
}
|
||||
wap.new = res
|
||||
|
||||
if err = store.CreateAutomationTrigger(ctx, s, res); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = label.Create(ctx, s, res); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Ignore workflow issues as those are defined by the workflow itself.
|
||||
// Internal errors should still be reported.
|
||||
if err = svc.registerWorkflow(ctx, wf, res); err != nil {
|
||||
if _, ok := err.(types.WorkflowIssueSet); ok {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
|
||||
return res, svc.recordAction(ctx, wap, TriggerActionCreate, err)
|
||||
}
|
||||
|
||||
// Update modifies existing trigger resource in the store
|
||||
func (svc *trigger) Update(ctx context.Context, upd *types.Trigger) (*types.Trigger, error) {
|
||||
return svc.updater(ctx, upd.ID, TriggerActionUpdate, func(ctx context.Context, res *types.Trigger) (triggerChanges, error) {
|
||||
if err := svc.canManageTrigger(ctx, res, TriggerErrNotAllowedToUpdate()); err != nil {
|
||||
return triggerUnchanged, err
|
||||
}
|
||||
|
||||
handler := svc.handleUpdate(upd)
|
||||
return handler(ctx, res)
|
||||
})
|
||||
}
|
||||
|
||||
func (svc *trigger) DeleteByID(ctx context.Context, triggerID uint64) error {
|
||||
return trim1st(svc.updater(ctx, triggerID, TriggerActionDelete, svc.handleDelete))
|
||||
}
|
||||
|
||||
func (svc *trigger) UndeleteByID(ctx context.Context, triggerID uint64) error {
|
||||
return trim1st(svc.updater(ctx, triggerID, TriggerActionUndelete, svc.handleUndelete))
|
||||
}
|
||||
|
||||
func (svc trigger) updater(ctx context.Context, triggerID uint64, action func(...*triggerActionProps) *triggerAction, fn triggerUpdateHandler) (*types.Trigger, error) {
|
||||
var (
|
||||
changes triggerChanges
|
||||
res *types.Trigger
|
||||
aProps = &triggerActionProps{trigger: &types.Trigger{ID: triggerID}}
|
||||
err error
|
||||
)
|
||||
|
||||
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
|
||||
res, err = loadTrigger(ctx, s, triggerID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = label.Load(ctx, svc.store, res); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aProps.setTrigger(res)
|
||||
aProps.setUpdate(res)
|
||||
|
||||
if changes, err = fn(ctx, res); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if changes&triggerChanged > 0 {
|
||||
if err = store.UpdateAutomationTrigger(ctx, svc.store, res); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if changes&triggerLabelsChanged > 0 {
|
||||
if err = label.Update(ctx, s, res); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
|
||||
return res, svc.recordAction(ctx, aProps, action, err)
|
||||
}
|
||||
|
||||
func (svc trigger) handleUpdate(upd *types.Trigger) triggerUpdateHandler {
|
||||
return func(ctx context.Context, res *types.Trigger) (changes triggerChanges, err error) {
|
||||
if isStale(upd.UpdatedAt, res.UpdatedAt, res.CreatedAt) {
|
||||
return triggerUnchanged, TriggerErrStaleData()
|
||||
}
|
||||
|
||||
if res.Enabled != upd.Enabled {
|
||||
changes |= triggerChanged
|
||||
res.Enabled = upd.Enabled
|
||||
}
|
||||
|
||||
if upd.Labels != nil {
|
||||
if label.Changed(res.Labels, upd.Labels) {
|
||||
changes |= triggerLabelsChanged
|
||||
res.Labels = upd.Labels
|
||||
}
|
||||
}
|
||||
|
||||
if res.StepID != upd.StepID {
|
||||
changes |= triggerChanged
|
||||
res.StepID = upd.StepID
|
||||
}
|
||||
|
||||
if res.EventType != upd.EventType {
|
||||
changes |= triggerChanged
|
||||
res.EventType = upd.EventType
|
||||
}
|
||||
|
||||
if res.ResourceType != upd.ResourceType {
|
||||
changes |= triggerChanged
|
||||
res.ResourceType = upd.ResourceType
|
||||
}
|
||||
|
||||
if upd.Meta != nil {
|
||||
if !reflect.DeepEqual(upd.Meta, res.Meta) {
|
||||
changes |= triggerChanged
|
||||
res.Meta = upd.Meta
|
||||
}
|
||||
}
|
||||
|
||||
if upd.Input != nil {
|
||||
if !reflect.DeepEqual(upd.Input, res.Input) {
|
||||
changes |= triggerChanged
|
||||
res.Input = upd.Input
|
||||
}
|
||||
}
|
||||
|
||||
if upd.Constraints != nil {
|
||||
if !reflect.DeepEqual(upd.Constraints, res.Constraints) {
|
||||
changes |= triggerChanged
|
||||
res.Constraints = upd.Constraints
|
||||
}
|
||||
}
|
||||
|
||||
if res.OwnedBy != upd.OwnedBy {
|
||||
// @todo need to check against access control if current user can modify owner
|
||||
changes |= triggerChanged
|
||||
res.OwnedBy = upd.OwnedBy
|
||||
}
|
||||
|
||||
if changes&triggerChanged > 0 {
|
||||
res.UpdatedAt = now()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (svc trigger) handleDelete(ctx context.Context, res *types.Trigger) (triggerChanges, error) {
|
||||
if err := svc.canManageTrigger(ctx, res, TriggerErrNotAllowedToDelete()); err != nil {
|
||||
return triggerUnchanged, err
|
||||
}
|
||||
|
||||
if res.DeletedAt != nil {
|
||||
// trigger already deleted
|
||||
return triggerUnchanged, nil
|
||||
}
|
||||
|
||||
res.DeletedAt = now()
|
||||
return triggerChanged, nil
|
||||
}
|
||||
|
||||
func (svc trigger) handleUndelete(ctx context.Context, res *types.Trigger) (triggerChanges, error) {
|
||||
if err := svc.canManageTrigger(ctx, res, TriggerErrNotAllowedToUndelete()); err != nil {
|
||||
return triggerUnchanged, err
|
||||
}
|
||||
|
||||
if res.DeletedAt == nil {
|
||||
// trigger not deleted
|
||||
return triggerUnchanged, nil
|
||||
}
|
||||
|
||||
res.DeletedAt = nil
|
||||
return triggerChanged, nil
|
||||
}
|
||||
|
||||
func (svc trigger) canManageTrigger(ctx context.Context, res *types.Trigger, permErr error) error {
|
||||
if wf, err := loadWorkflow(ctx, svc.store, res.WorkflowID); err != nil {
|
||||
return err
|
||||
} else if !svc.ac.CanManageTriggersOnWorkflow(ctx, wf) {
|
||||
return permErr
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// registers all triggers on all given workflows
|
||||
// before registering triggers on a workflow, all workflow triggers are unregistered
|
||||
func (svc *trigger) registerWorkflows(ctx context.Context, workflows ...*types.Workflow) error {
|
||||
// load ALL triggers directly from store
|
||||
tt, _, err := store.SearchAutomationTriggers(ctx, svc.store, types.TriggerFilter{
|
||||
WorkflowID: id.Strings(types.WorkflowSet(workflows).IDs()...),
|
||||
Deleted: filter.StateInclusive,
|
||||
Disabled: filter.StateExcluded,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, wf := range workflows {
|
||||
svc.unregisterWorkflows(wf)
|
||||
|
||||
if !wf.Enabled {
|
||||
continue
|
||||
}
|
||||
|
||||
if wf.DeletedAt != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(wf.Issues) > 0 {
|
||||
// workflow was processed before and issues were detected
|
||||
// and stored on the workflow; no need to continue
|
||||
continue
|
||||
}
|
||||
|
||||
if err = svc.registerWorkflow(ctx, wf, tt.FilterByWorkflowID(wf.ID)...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updates trigger handler registration
|
||||
//
|
||||
// Loads associated workflow and registers specific trigger
|
||||
func (svc *trigger) updateTriggerRegistration(ctx context.Context, t *types.Trigger) error {
|
||||
wf, err := loadWorkflow(ctx, svc.store, t.WorkflowID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return svc.registerWorkflow(ctx, wf, t)
|
||||
}
|
||||
|
||||
// registers one workflow and a set of triggers
|
||||
func (svc *trigger) registerWorkflow(ctx context.Context, wf *types.Workflow, tt ...*types.Trigger) (err error) {
|
||||
var (
|
||||
runAs auth.Identifiable
|
||||
)
|
||||
|
||||
// Returns context with identity set to service user
|
||||
//
|
||||
// Current user (identity in the context) might not have
|
||||
// sufficient privileges to load info about invoker and runner
|
||||
sysUserCtx := func() context.Context {
|
||||
return auth.SetIdentityToContext(ctx, auth.ServiceUser())
|
||||
}
|
||||
|
||||
if !svc.opt.Register {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !wf.Enabled || len(wf.Issues) > 0 {
|
||||
// do not even try to register disabled
|
||||
// workflows or workflows with issues
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(types.TriggerSet(tt).FilterByWorkflowID(wf.ID)) < len(tt) {
|
||||
return fmt.Errorf("all triggers must reference the given workflow")
|
||||
}
|
||||
|
||||
if wis := validateWorkflowTriggers(wf, tt...); len(wis) > 0 {
|
||||
// skip trigger registration of there is a trigger related issue(s)
|
||||
// on a specific workflow.
|
||||
//
|
||||
// this really happens since we run all validation on save,
|
||||
// but there might be workflow from the time when these checks were
|
||||
// not in place
|
||||
return nil
|
||||
}
|
||||
|
||||
if wf.RunAs > 0 {
|
||||
if runAs, err = DefaultUser.FindByAny(sysUserCtx(), wf.RunAs); err != nil {
|
||||
return fmt.Errorf("failed to load run-as user %d: %w", wf.RunAs, err)
|
||||
} else if !runAs.Valid() {
|
||||
return fmt.Errorf("invalid user %d used for workflow run-as", wf.RunAs)
|
||||
}
|
||||
}
|
||||
|
||||
svc.registerTriggers(wf, runAs, tt...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// registerTriggers registers workflows triggers to eventbus
|
||||
//
|
||||
// It preloads run-as identity and finds a starting step for each trigger
|
||||
func (svc *trigger) registerTriggers(wf *types.Workflow, runAs auth.Identifiable, tt ...*types.Trigger) {
|
||||
var (
|
||||
handlerFn eventbus.HandlerFn
|
||||
err error
|
||||
g *wfexec.Graph
|
||||
issues types.WorkflowIssueSet
|
||||
wfLog = svc.log.
|
||||
With(logger.Uint64("workflowID", wf.ID))
|
||||
|
||||
// register only enabled, undeleted workflows
|
||||
registerWorkflow = wf.Enabled && wf.DeletedAt == nil
|
||||
)
|
||||
|
||||
// convert only registrable and workflows without issues
|
||||
if registerWorkflow && len(wf.Issues) == 0 {
|
||||
// Convert workflow only when valid (no issues, enable, not delete)
|
||||
if g, issues = Convert(svc.workflow, wf); len(issues) > 0 {
|
||||
wfLog.Error("failed to convert workflow to graph", zap.Error(issues))
|
||||
_ = issues.Walk(func(i *types.WorkflowIssue) error {
|
||||
wfLog.Debug("workflow issue found: "+i.Description, zap.Any("culprit", i.Culprit))
|
||||
return nil
|
||||
})
|
||||
g = nil
|
||||
}
|
||||
}
|
||||
|
||||
defer svc.mux.Unlock()
|
||||
svc.mux.Lock()
|
||||
|
||||
for _, t := range tt {
|
||||
log := wfLog.With(logger.Uint64("triggerID", t.ID))
|
||||
|
||||
// always unregister
|
||||
if svc.reg[wf.ID] == nil {
|
||||
svc.reg[wf.ID] = make(map[uint64]uintptr)
|
||||
} else if ptr := svc.reg[wf.ID][t.ID]; ptr != 0 {
|
||||
// unregister handlers for this trigger if they exist
|
||||
svc.eventbus.Unregister(ptr)
|
||||
}
|
||||
|
||||
// do not register disabled or deleted triggers
|
||||
if !registerWorkflow || !t.Enabled || t.DeletedAt != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var (
|
||||
cnstr eventbus.ConstraintMatcher
|
||||
ops = make([]eventbus.HandlerRegOp, 0, len(t.Constraints)+2)
|
||||
)
|
||||
|
||||
if g == nil {
|
||||
handlerFn = func(_ context.Context, ev eventbus.Event) error {
|
||||
return errors.Internal(
|
||||
"trigger %s on %s failed due to invalid workflow %d: %s",
|
||||
ev.EventType(),
|
||||
ev.ResourceType(),
|
||||
wf.ID,
|
||||
wf.Issues,
|
||||
).Wrap(wf.Issues)
|
||||
}
|
||||
} else {
|
||||
handlerFn = makeWorkflowHandler(svc.workflow, wf, t)
|
||||
}
|
||||
|
||||
ops = append(
|
||||
ops,
|
||||
eventbus.On(t.EventType),
|
||||
eventbus.For(t.ResourceType),
|
||||
)
|
||||
|
||||
for _, c := range t.Constraints {
|
||||
if cnstr, err = eventbus.ConstraintMaker(c.Name, c.Op, c.Values...); err != nil {
|
||||
log.Debug(
|
||||
"failed to make constraint for workflow trigger",
|
||||
zap.Any("constraint", c),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
ops = append(ops, eventbus.Constraint(cnstr))
|
||||
}
|
||||
}
|
||||
|
||||
svc.reg[wf.ID][t.ID] = svc.eventbus.Register(handlerFn, ops...)
|
||||
|
||||
log.Debug("trigger registered",
|
||||
zap.String("eventType", t.EventType),
|
||||
zap.String("resourceType", t.ResourceType),
|
||||
zap.Any("constraints", t.Constraints),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *trigger) unregisterWorkflows(wwf ...*types.Workflow) {
|
||||
defer svc.mux.Unlock()
|
||||
svc.mux.Lock()
|
||||
|
||||
for _, wf := range wwf {
|
||||
for triggerID, ptr := range svc.reg[wf.ID] {
|
||||
svc.eventbus.Unregister(ptr)
|
||||
svc.log.Debug("trigger unregistered", logger.Uint64("triggerID", triggerID), logger.Uint64("workflowID", wf.ID))
|
||||
delete(svc.triggers, wf.ID)
|
||||
}
|
||||
|
||||
delete(svc.reg, wf.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func (svc *trigger) unregisterTriggers(tt ...*types.Trigger) {
|
||||
defer svc.mux.Unlock()
|
||||
svc.mux.Lock()
|
||||
|
||||
for _, t := range tt {
|
||||
if svc.reg[t.WorkflowID] == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if ptr, has := svc.reg[t.WorkflowID][t.ID]; has {
|
||||
svc.eventbus.Unregister(ptr)
|
||||
svc.log.Debug("trigger unregistered", logger.Uint64("triggerID", t.ID), logger.Uint64("workflowID", t.WorkflowID))
|
||||
delete(svc.triggers, t.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadTrigger(ctx context.Context, s store.Storer, triggerID uint64) (res *types.Trigger, err error) {
|
||||
if triggerID == 0 {
|
||||
return nil, TriggerErrInvalidID()
|
||||
}
|
||||
|
||||
if res, err = store.LookupAutomationTriggerByID(ctx, s, triggerID); errors.IsNotFound(err) {
|
||||
return nil, TriggerErrNotFound()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func loadWorkflowTriggers(ctx context.Context, s store.Storer, workflowID uint64) (tt types.TriggerSet, err error) {
|
||||
if workflowID == 0 {
|
||||
return nil, TriggerErrInvalidID()
|
||||
}
|
||||
|
||||
if tt, _, err = store.SearchAutomationTriggers(ctx, s, types.TriggerFilter{WorkflowID: id.Strings(workflowID)}); errors.IsNotFound(err) {
|
||||
return nil, TriggerErrNotFound()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// toLabeledTriggers converts to []label.LabeledResource
|
||||
func toLabeledTriggers(set []*types.Trigger) []label.LabeledResource {
|
||||
if len(set) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ll := make([]label.LabeledResource, len(set))
|
||||
for i := range set {
|
||||
ll[i] = set[i]
|
||||
}
|
||||
|
||||
return ll
|
||||
}
|
||||
|
||||
// Checks if triggers are compatible with the workflow
|
||||
//
|
||||
// It ignores disabled triggers and does not care if triggers are in fact bond to the
|
||||
// given workflow
|
||||
func validateWorkflowTriggers(wf *types.Workflow, tt ...*types.Trigger) (wis types.WorkflowIssueSet) {
|
||||
var (
|
||||
// @todo find a better way how to flag events type that require
|
||||
// run-as param to be set
|
||||
// Possible solution: flag in definition that generates static
|
||||
// list w/o the need of cross-component imports
|
||||
requireRunAs = []eventbus.Event{
|
||||
sysEvent.SinkOnRequest(nil, nil),
|
||||
sysEvent.QueueOnMessage(nil),
|
||||
sysEvent.SystemOnInterval(),
|
||||
sysEvent.SystemOnTimestamp(),
|
||||
cmpEvent.ComposeOnInterval(),
|
||||
cmpEvent.ComposeOnTimestamp(),
|
||||
}
|
||||
)
|
||||
|
||||
for i, t := range tt {
|
||||
if !t.Enabled || t.DeletedAt != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, ev := range requireRunAs {
|
||||
if t.ResourceType == ev.ResourceType() && t.EventType == ev.EventType() {
|
||||
if wf.RunAs == 0 {
|
||||
wis = wis.Append(
|
||||
errors.InvalidData("%s for %s requires run-as to be set", ev.ResourceType(), ev.EventType()),
|
||||
map[string]int{"trigger": i},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if wf.Meta != nil && wf.Meta.SubWorkflow {
|
||||
wis = wis.Append(
|
||||
errors.InvalidData("workflow marked as sub-workflow cannot have enabled triggers"),
|
||||
map[string]int{"trigger": i},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user