Make automation S&T namespace bound
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -0,0 +1,11 @@
|
||||
ALTER TABLE `compose_automation_script`
|
||||
ADD `rel_namespace` BIGINT UNSIGNED NOT NULL AFTER `id`,
|
||||
ADD INDEX (`rel_namespace`);
|
||||
|
||||
UPDATE `compose_automation_script` SET `rel_namespace` = (SELECT MIN(id) FROM compose_namespace);
|
||||
|
||||
ALTER TABLE `compose_automation_script`
|
||||
ADD CONSTRAINT `compose_automation_script_namespace`
|
||||
FOREIGN KEY (`rel_namespace`)
|
||||
REFERENCES `compose_namespace` (`id`);
|
||||
|
||||
@@ -18,6 +18,7 @@ type (
|
||||
ns NamespaceService
|
||||
mod ModuleService
|
||||
ac automationScriptAccessController
|
||||
trg automationTrigger
|
||||
}
|
||||
|
||||
automationScriptManager interface {
|
||||
@@ -38,12 +39,6 @@ type (
|
||||
CanReadAutomationScript(context.Context, *automation.Script) bool
|
||||
CanUpdateAutomationScript(context.Context, *automation.Script) bool
|
||||
CanDeleteAutomationScript(context.Context, *automation.Script) bool
|
||||
|
||||
CanManageAutomationTriggersOnModule(context.Context, *types.Module) bool
|
||||
}
|
||||
|
||||
automationScriptNamespaceFinder interface {
|
||||
FindByID(uint64) (*types.Namespace, error)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -54,6 +49,7 @@ func AutomationScript(sm automationScriptManager) automationScript {
|
||||
ac: DefaultAccessControl,
|
||||
mod: DefaultModule,
|
||||
ns: DefaultNamespace,
|
||||
trg: DefaultAutomationTriggerManager,
|
||||
}
|
||||
|
||||
return svc
|
||||
@@ -72,6 +68,7 @@ func (svc automationScript) Find(ctx context.Context, namespaceID uint64, f auto
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
f.NamespaceID = namespaceID
|
||||
f.AccessCheck = permissions.InitAccessCheckFilter(
|
||||
"read",
|
||||
auth.GetIdentityFromContext(ctx).Roles(),
|
||||
@@ -99,7 +96,7 @@ func (svc automationScript) Create(ctx context.Context, namespaceID uint64, mod
|
||||
}
|
||||
|
||||
err = mod.Triggers().Walk(func(t *automation.Trigger) error {
|
||||
return svc.isValidTrigger(ctx, t, namespaceID)
|
||||
return svc.trg.isValid(ctx, mod, t)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -139,7 +136,7 @@ func (svc automationScript) Update(ctx context.Context, namespaceID uint64, mod
|
||||
s.Enabled = mod.Enabled
|
||||
|
||||
err = mod.Triggers().Walk(func(t *automation.Trigger) error {
|
||||
return svc.isValidTrigger(ctx, t, namespaceID)
|
||||
return svc.trg.isValid(ctx, mod, t)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -185,36 +182,3 @@ func (svc automationScript) loadCombo(ctx context.Context, namespaceID, scriptID
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (svc automationScript) isValidTrigger(ctx context.Context, t *automation.Trigger, namespaceID uint64) error {
|
||||
if t.Resource != "compose:record" {
|
||||
// Accepting only compose:record resources
|
||||
return automation.ErrAutomationTriggerInvalidResource
|
||||
}
|
||||
|
||||
if t.IsDeferred() {
|
||||
// @todo validate condition for deferred triggers
|
||||
return nil
|
||||
}
|
||||
|
||||
switch t.Event {
|
||||
case "manual",
|
||||
"beforeCreate", "beforeUpdate", "beforeDelete",
|
||||
"afterCreate", "afterUpdate", "afterDelete":
|
||||
var moduleID = t.Uint64Condition()
|
||||
|
||||
if t.Event != "manual" && moduleID == 0 {
|
||||
return automation.ErrAutomationTriggerInvalidCondition
|
||||
}
|
||||
|
||||
if m, err := svc.mod.With(ctx).FindByID(namespaceID, moduleID); err != nil {
|
||||
return err
|
||||
} else if !svc.ac.CanManageAutomationTriggersOnModule(ctx, m) {
|
||||
return ErrNoTriggerManagementPermissions
|
||||
}
|
||||
default:
|
||||
return automation.ErrAutomationTriggerInvalidEvent
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/automation"
|
||||
)
|
||||
|
||||
@@ -13,6 +14,9 @@ type (
|
||||
automationTrigger struct {
|
||||
logger *zap.Logger
|
||||
triggerManager automationTriggerManager
|
||||
|
||||
mod ModuleService
|
||||
ac automationTriggerAccessController
|
||||
}
|
||||
|
||||
automationTriggerManager interface {
|
||||
@@ -22,12 +26,20 @@ type (
|
||||
UpdateTrigger(context.Context, *automation.Script, *automation.Trigger) error
|
||||
DeleteTrigger(context.Context, *automation.Trigger) error
|
||||
}
|
||||
|
||||
automationTriggerAccessController interface {
|
||||
CanUpdateAutomationScript(context.Context, *automation.Script) bool
|
||||
CanManageAutomationTriggersOnModule(context.Context, *types.Module) bool
|
||||
}
|
||||
)
|
||||
|
||||
func AutomationTrigger(tm automationTriggerManager) automationTrigger {
|
||||
var svc = automationTrigger{
|
||||
triggerManager: tm,
|
||||
logger: DefaultLogger.Named("automation-trigger"),
|
||||
|
||||
ac: DefaultAccessControl,
|
||||
mod: DefaultModule,
|
||||
}
|
||||
|
||||
return svc
|
||||
@@ -44,16 +56,77 @@ func (svc automationTrigger) Find(ctx context.Context, f automation.TriggerFilte
|
||||
}
|
||||
|
||||
func (svc automationTrigger) Create(ctx context.Context, s *automation.Script, t *automation.Trigger) (err error) {
|
||||
// @todo security check - can user create trigger on this specific resource
|
||||
if err = svc.isValid(ctx, s, t); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !svc.ac.CanUpdateAutomationScript(ctx, s) {
|
||||
return ErrNoTriggerManagementPermissions
|
||||
}
|
||||
|
||||
return svc.triggerManager.CreateTrigger(ctx, s, t)
|
||||
}
|
||||
|
||||
func (svc automationTrigger) Update(ctx context.Context, s *automation.Script, t *automation.Trigger) (err error) {
|
||||
// @todo security check - can user create update triggers on this specific resource
|
||||
if err = svc.isValid(ctx, s, t); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !svc.ac.CanUpdateAutomationScript(ctx, s) {
|
||||
return ErrNoTriggerManagementPermissions
|
||||
}
|
||||
|
||||
return svc.triggerManager.UpdateTrigger(ctx, s, t)
|
||||
}
|
||||
|
||||
func (svc automationTrigger) Delete(ctx context.Context, t *automation.Trigger) (err error) {
|
||||
// @todo security check - can user create delete triggers on this specific resource
|
||||
func (svc automationTrigger) Delete(ctx context.Context, s *automation.Script, t *automation.Trigger) (err error) {
|
||||
if err = svc.isValid(ctx, s, t); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !svc.ac.CanUpdateAutomationScript(ctx, s) {
|
||||
return ErrNoTriggerManagementPermissions
|
||||
}
|
||||
|
||||
return svc.triggerManager.DeleteTrigger(ctx, t)
|
||||
}
|
||||
|
||||
// Validates trigger (in compose context)
|
||||
func (svc automationTrigger) isValid(ctx context.Context, s *automation.Script, t *automation.Trigger) error {
|
||||
if !t.Enabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
if t.Resource != "compose:record" {
|
||||
// Accepting only compose:record resources
|
||||
return automation.ErrAutomationTriggerInvalidResource
|
||||
}
|
||||
|
||||
if t.IsDeferred() {
|
||||
// @todo validate condition for deferred triggers
|
||||
return nil
|
||||
}
|
||||
|
||||
switch t.Event {
|
||||
case "manual",
|
||||
"beforeCreate", "beforeUpdate", "beforeDelete",
|
||||
"afterCreate", "afterUpdate", "afterDelete":
|
||||
var moduleID = t.Uint64Condition()
|
||||
|
||||
if t.Event != "manual" && moduleID == 0 {
|
||||
return automation.ErrAutomationTriggerInvalidCondition
|
||||
}
|
||||
|
||||
if moduleID > 0 {
|
||||
if m, err := svc.mod.With(ctx).FindByID(s.NamespaceID, moduleID); err != nil {
|
||||
return err
|
||||
} else if !svc.ac.CanManageAutomationTriggersOnModule(ctx, m) {
|
||||
return ErrNoTriggerManagementPermissions
|
||||
}
|
||||
}
|
||||
default:
|
||||
return automation.ErrAutomationTriggerInvalidEvent
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -102,8 +102,8 @@ func Init(ctx context.Context, log *zap.Logger, c Config) (err error) {
|
||||
})
|
||||
|
||||
// Pass automation manager to
|
||||
DefaultAutomationScriptManager = AutomationScript(ias)
|
||||
DefaultAutomationTriggerManager = AutomationTrigger(ias)
|
||||
DefaultAutomationScriptManager = AutomationScript(ias)
|
||||
|
||||
{
|
||||
var scriptRunnerClient corredor.ScriptRunnerClient
|
||||
|
||||
@@ -96,9 +96,7 @@ func (AutomationScript) New() *AutomationScript {
|
||||
|
||||
func (ctrl AutomationScript) List(ctx context.Context, r *request.AutomationScriptList) (interface{}, error) {
|
||||
set, filter, err := ctrl.scripts.Find(ctx, r.NamespaceID, automation.ScriptFilter{
|
||||
// @todo namespace filtering
|
||||
// Might be a bit tricky as scripts themselves not know about namespaces
|
||||
// Namespace: r.NamespaceID
|
||||
NamespaceID: r.NamespaceID,
|
||||
|
||||
Query: r.Query,
|
||||
Resource: r.Resource,
|
||||
@@ -113,15 +111,16 @@ func (ctrl AutomationScript) List(ctx context.Context, r *request.AutomationScri
|
||||
func (ctrl AutomationScript) Create(ctx context.Context, r *request.AutomationScriptCreate) (interface{}, error) {
|
||||
var (
|
||||
script = &automation.Script{
|
||||
Name: r.Name,
|
||||
SourceRef: r.SourceRef,
|
||||
Source: r.Source,
|
||||
Async: r.Async,
|
||||
RunAs: r.RunAs,
|
||||
RunInUA: r.RunInUA,
|
||||
Timeout: r.Timeout,
|
||||
Critical: r.Critical,
|
||||
Enabled: r.Enabled,
|
||||
NamespaceID: r.NamespaceID,
|
||||
Name: r.Name,
|
||||
SourceRef: r.SourceRef,
|
||||
Source: r.Source,
|
||||
Async: r.Async,
|
||||
RunAs: r.RunAs,
|
||||
RunInUA: r.RunInUA,
|
||||
Timeout: r.Timeout,
|
||||
Critical: r.Critical,
|
||||
Enabled: r.Enabled,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -137,16 +136,17 @@ func (ctrl AutomationScript) Read(ctx context.Context, r *request.AutomationScri
|
||||
|
||||
func (ctrl AutomationScript) Update(ctx context.Context, r *request.AutomationScriptUpdate) (interface{}, error) {
|
||||
mod := &automation.Script{
|
||||
ID: r.ScriptID,
|
||||
Name: r.Name,
|
||||
SourceRef: r.SourceRef,
|
||||
Source: r.Source,
|
||||
Async: r.Async,
|
||||
RunAs: r.RunAs,
|
||||
RunInUA: r.RunInUA,
|
||||
Timeout: r.Timeout,
|
||||
Critical: r.Critical,
|
||||
Enabled: r.Enabled,
|
||||
ID: r.ScriptID,
|
||||
NamespaceID: r.NamespaceID,
|
||||
Name: r.Name,
|
||||
SourceRef: r.SourceRef,
|
||||
Source: r.Source,
|
||||
Async: r.Async,
|
||||
RunAs: r.RunAs,
|
||||
RunInUA: r.RunInUA,
|
||||
Timeout: r.Timeout,
|
||||
Critical: r.Critical,
|
||||
Enabled: r.Enabled,
|
||||
}
|
||||
|
||||
mod.AddTrigger(automation.STMS_UPDATE, r.Triggers...)
|
||||
@@ -166,6 +166,10 @@ func (ctrl AutomationScript) Runnable(ctx context.Context, r *request.Automation
|
||||
)
|
||||
|
||||
return rval, ctrl.runner.UserScripts(ctx).Walk(func(script *automation.Script) error {
|
||||
if script.NamespaceID != r.NamespaceID {
|
||||
return nil
|
||||
}
|
||||
|
||||
// @todo filter out all modules (by t.Condition) we do not have access to
|
||||
out := &automationScriptRunnable{
|
||||
ScriptID: script.ID,
|
||||
|
||||
@@ -4,7 +4,9 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/titpetric/factory/resputil"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/compose/internal/repository"
|
||||
"github.com/cortezaproject/corteza-server/compose/internal/service"
|
||||
"github.com/cortezaproject/corteza-server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza-server/pkg/automation"
|
||||
@@ -37,7 +39,7 @@ type (
|
||||
Find(context.Context, automation.TriggerFilter) (automation.TriggerSet, automation.TriggerFilter, error)
|
||||
Create(context.Context, *automation.Script, *automation.Trigger) error
|
||||
Update(context.Context, *automation.Script, *automation.Trigger) error
|
||||
Delete(context.Context, *automation.Trigger) error
|
||||
Delete(context.Context, *automation.Script, *automation.Trigger) error
|
||||
}
|
||||
|
||||
automationScriptFinderService interface {
|
||||
@@ -77,25 +79,22 @@ func (ctrl AutomationTrigger) List(ctx context.Context, r *request.AutomationTri
|
||||
}
|
||||
|
||||
func (ctrl AutomationTrigger) Create(ctx context.Context, r *request.AutomationTriggerCreate) (interface{}, error) {
|
||||
// @todo trigger management is currently done through automation-script endpoints
|
||||
return nil, errors.New("direct trigger management disabled")
|
||||
s, _, err := ctrl.loadCombo(ctx, r.NamespaceID, r.ScriptID, 0)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can not create trigger")
|
||||
}
|
||||
|
||||
// s, _, err := ctrl.loadCombo(ctx, r.NamespaceID, r.ScriptID, 0)
|
||||
// if err != nil {
|
||||
// return nil, errors.Wrap(err, "can not create trigger")
|
||||
// }
|
||||
//
|
||||
// var (
|
||||
// t = &automation.Trigger{
|
||||
// Event: r.Event,
|
||||
// Resource: r.Resource,
|
||||
// Condition: r.Condition,
|
||||
// ScriptID: s.ID,
|
||||
// Enabled: r.Enabled,
|
||||
// }
|
||||
// )
|
||||
//
|
||||
// return ctrl.makePayload(ctx, t, ctrl.triggers.Create(ctx, s, t))
|
||||
var (
|
||||
t = &automation.Trigger{
|
||||
Event: r.Event,
|
||||
Resource: r.Resource,
|
||||
Condition: r.Condition,
|
||||
ScriptID: s.ID,
|
||||
Enabled: r.Enabled,
|
||||
}
|
||||
)
|
||||
|
||||
return ctrl.makePayload(ctx, t, ctrl.triggers.Create(ctx, s, t))
|
||||
}
|
||||
|
||||
func (ctrl AutomationTrigger) Read(ctx context.Context, r *request.AutomationTriggerRead) (interface{}, error) {
|
||||
@@ -108,33 +107,27 @@ func (ctrl AutomationTrigger) Read(ctx context.Context, r *request.AutomationTri
|
||||
}
|
||||
|
||||
func (ctrl AutomationTrigger) Update(ctx context.Context, r *request.AutomationTriggerUpdate) (interface{}, error) {
|
||||
// @todo trigger management is currently done through automation-script endpoints
|
||||
return nil, errors.New("direct trigger management disabled")
|
||||
s, t, err := ctrl.loadCombo(ctx, r.NamespaceID, r.ScriptID, r.TriggerID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can not update trigger")
|
||||
}
|
||||
|
||||
// s, t, err := ctrl.loadCombo(ctx, r.NamespaceID, r.ScriptID, r.TriggerID)
|
||||
// if err != nil {
|
||||
// return nil, errors.Wrap(err, "can not update trigger")
|
||||
// }
|
||||
//
|
||||
// t.Event = r.Event
|
||||
// t.Resource = r.Resource
|
||||
// t.Condition = r.Condition
|
||||
// t.ScriptID = r.ScriptID
|
||||
// t.Enabled = r.Enabled
|
||||
//
|
||||
// return ctrl.makePayload(ctx, t, ctrl.triggers.Update(ctx, s, t))
|
||||
t.Event = r.Event
|
||||
t.Resource = r.Resource
|
||||
t.Condition = r.Condition
|
||||
t.ScriptID = r.ScriptID
|
||||
t.Enabled = r.Enabled
|
||||
|
||||
return ctrl.makePayload(ctx, t, ctrl.triggers.Update(ctx, s, t))
|
||||
}
|
||||
|
||||
func (ctrl AutomationTrigger) Delete(ctx context.Context, r *request.AutomationTriggerDelete) (interface{}, error) {
|
||||
// @todo trigger management is currently done through automation-script endpoints
|
||||
return nil, errors.New("direct trigger management disabled")
|
||||
s, t, err := ctrl.loadCombo(ctx, r.NamespaceID, r.ScriptID, r.TriggerID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can not update trigger")
|
||||
}
|
||||
|
||||
// trigger, err := ctrl.triggers.FindByID(ctx, r.TriggerID)
|
||||
// if err != nil {
|
||||
// return nil, errors.Wrap(err, "can not delete trigger")
|
||||
// }
|
||||
//
|
||||
// return resputil.OK(), ctrl.triggers.Delete(ctx, trigger)
|
||||
return resputil.OK(), ctrl.triggers.Delete(ctx, s, t)
|
||||
}
|
||||
|
||||
func (ctrl AutomationTrigger) loadCombo(ctx context.Context, namespaceID, scriptID, triggerID uint64) (s *automation.Script, t *automation.Trigger, err error) {
|
||||
@@ -145,6 +138,10 @@ func (ctrl AutomationTrigger) loadCombo(ctx context.Context, namespaceID, script
|
||||
|
||||
if scriptID > 0 {
|
||||
s, err = ctrl.scripts.FindByID(ctx, namespaceID, scriptID)
|
||||
if err != nil && s.NamespaceID != namespaceID {
|
||||
err = repository.ErrNamespaceNotFound
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ type (
|
||||
Script struct {
|
||||
ID uint64 `json:"scriptID,string" db:"id"`
|
||||
|
||||
NamespaceID uint64 `json:"namespaceID,string" db:"rel_namespace"`
|
||||
|
||||
Name string `json:"name" db:"name"`
|
||||
|
||||
// (URL) Where did we get the source from?
|
||||
@@ -67,6 +69,8 @@ type (
|
||||
}
|
||||
|
||||
ScriptFilter struct {
|
||||
NamespaceID uint64 `json:"namespaceID,string"`
|
||||
|
||||
Query string
|
||||
Resource string
|
||||
IncDeleted bool `json:"incDeleted"`
|
||||
|
||||
@@ -32,6 +32,7 @@ func (r scriptRepository) table() string {
|
||||
func (r scriptRepository) columns() []string {
|
||||
return []string{
|
||||
"id",
|
||||
"rel_namespace",
|
||||
"name",
|
||||
"source_ref",
|
||||
"source",
|
||||
@@ -74,10 +75,14 @@ func (r *scriptRepository) find(db *factory.DB, filter ScriptFilter) (set Script
|
||||
|
||||
query := r.query()
|
||||
|
||||
if !filter.IncDeleted {
|
||||
if !f.IncDeleted {
|
||||
query = query.Where("deleted_at IS NULL")
|
||||
}
|
||||
|
||||
if f.NamespaceID > 0 {
|
||||
query = query.Where("rel_namespace = ?", f.NamespaceID)
|
||||
}
|
||||
|
||||
if f.Query != "" {
|
||||
q := "%" + f.Query + "%"
|
||||
query = query.Where("name like ?", q)
|
||||
|
||||
Reference in New Issue
Block a user