3
0
corteza/compose/rest/automation_script.go
2019-09-09 13:04:12 +02:00

313 lines
8.7 KiB
Go

package rest
import (
"context"
"encoding/json"
"github.com/pkg/errors"
"github.com/titpetric/factory/resputil"
"github.com/cortezaproject/corteza-server/compose/rest/request"
"github.com/cortezaproject/corteza-server/compose/service"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/automation"
"github.com/cortezaproject/corteza-server/pkg/rh"
)
var _ = errors.Wrap
type (
automationScriptPayload struct {
*automation.Script
CanGrant bool `json:"canGrant"`
CanUpdate bool `json:"canUpdate"`
CanDelete bool `json:"canDelete"`
CanSetRunner bool `json:"canSetRunner"`
CanSetAsAsync bool `json:"canSetAsAsync"`
CanSetAsCritical bool `json:"canAsCritical"`
}
automationScriptSetPayload struct {
Filter automation.ScriptFilter `json:"filter"`
Set []*automationScriptPayload `json:"set"`
}
automationScriptRunnablePayload struct {
Set []*automationScriptRunnable `json:"set"`
}
automationScriptRunnable struct {
ScriptID uint64 `json:"scriptID,string"`
Name string `json:"name"`
Events map[string][]string `json:"events"`
Source string `json:"source,omitempty"`
Async bool `json:"async"`
RunInUA bool `json:"runInUA"`
}
automationScriptRun struct {
Record *types.Record `json:"record,omitempty"`
}
AutomationScript struct {
scripts automationScriptService
runner automationScriptRunner
ac automationScriptAccessController
namespace service.NamespaceService
module service.ModuleService
record service.RecordService
}
automationScriptService interface {
FindByID(context.Context, uint64, uint64) (*automation.Script, error)
Find(context.Context, uint64, automation.ScriptFilter) (automation.ScriptSet, automation.ScriptFilter, error)
Create(context.Context, uint64, *automation.Script) error
Update(context.Context, uint64, *automation.Script) error
Delete(context.Context, uint64, uint64) error
}
automationScriptRunner interface {
UserScripts(context.Context) automation.ScriptSet
RecordManual(context.Context, uint64, *types.Namespace, *types.Module, *types.Record) error
RecordScriptTester(context.Context, string, *types.Namespace, *types.Module, *types.Record) error
}
automationScriptAccessController interface {
CanGrant(context.Context) bool
CanUpdateAutomationScript(context.Context, *automation.Script) bool
CanDeleteAutomationScript(context.Context, *automation.Script) bool
}
)
func (AutomationScript) New() *AutomationScript {
return &AutomationScript{
scripts: service.DefaultAutomationScriptManager,
runner: service.DefaultAutomationRunner,
ac: service.DefaultAccessControl,
namespace: service.DefaultNamespace,
module: service.DefaultModule,
record: service.DefaultRecord,
}
}
func (ctrl AutomationScript) List(ctx context.Context, r *request.AutomationScriptList) (interface{}, error) {
set, filter, err := ctrl.scripts.Find(ctx, r.NamespaceID, automation.ScriptFilter{
NamespaceID: r.NamespaceID,
Query: r.Query,
Resource: r.Resource,
IncDeleted: false,
PageFilter: rh.Paging(r.Page, r.PerPage),
})
return ctrl.makeFilterPayload(ctx, set, filter, err)
}
func (ctrl AutomationScript) Create(ctx context.Context, r *request.AutomationScriptCreate) (interface{}, error) {
var (
script = &automation.Script{
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,
}
)
script.AddTrigger(automation.STMS_FRESH, r.Triggers...)
return ctrl.makePayload(ctx, script, ctrl.scripts.Create(ctx, r.NamespaceID, script))
}
func (ctrl AutomationScript) Read(ctx context.Context, r *request.AutomationScriptRead) (interface{}, error) {
script, err := ctrl.scripts.FindByID(ctx, r.NamespaceID, r.ScriptID)
return ctrl.makePayload(ctx, script, err)
}
func (ctrl AutomationScript) Update(ctx context.Context, r *request.AutomationScriptUpdate) (interface{}, error) {
mod := &automation.Script{
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...)
return ctrl.makePayload(ctx, mod, ctrl.scripts.Update(ctx, r.NamespaceID, mod))
}
func (ctrl AutomationScript) Delete(ctx context.Context, r *request.AutomationScriptDelete) (interface{}, error) {
return resputil.OK(), ctrl.scripts.Delete(ctx, r.NamespaceID, r.ScriptID)
}
func (ctrl AutomationScript) Runnable(ctx context.Context, r *request.AutomationScriptRunnable) (interface{}, error) {
var (
rval = &automationScriptRunnablePayload{
Set: make([]*automationScriptRunnable, 0),
}
)
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,
Name: script.Name,
Events: map[string][]string{},
Async: script.Async,
RunInUA: script.RunInUA,
}
if script.RunInUA {
out.Source = script.Source
}
_ = script.Triggers().Walk(func(t *automation.Trigger) error {
if r.Condition != "" && r.Condition != t.Condition {
// When not requesting explicit module and condition does not match (module id or 0)
// ignore
return nil
}
if _, ok := out.Events[t.Event]; ok {
out.Events[t.Event] = append(out.Events[t.Event], t.Condition)
} else {
out.Events[t.Event] = []string{t.Condition}
}
return nil
})
if len(out.Events) == 0 {
return nil
}
rval.Set = append(rval.Set, out)
return nil
})
}
func (ctrl AutomationScript) Run(ctx context.Context, r *request.AutomationScriptRun) (interface{}, error) {
var (
rval automationScriptRun
ns, m, record, err = ctrl.loadRecordScriptRunningCombo(ctx, r.NamespaceID, r.ModuleID, r.RecordID, r.Record)
)
if err != nil {
return nil, err
}
if err = ctrl.runner.RecordManual(ctx, r.ScriptID, ns, m, record); err != nil {
return nil, err
}
// When record was passed return it.
if record != nil {
rval.Record = record
}
return rval, err
}
func (ctrl AutomationScript) Test(ctx context.Context, r *request.AutomationScriptTest) (interface{}, error) {
var (
rval automationScriptRun
ns, m, record, err = ctrl.loadRecordScriptRunningCombo(ctx, r.NamespaceID, r.ModuleID, 0, r.Record)
)
if err != nil {
return nil, err
}
if err = ctrl.runner.RecordScriptTester(ctx, r.Source, ns, m, record); err != nil {
return nil, err
}
// When record was passed return it.
if record != nil {
rval.Record = record
}
return rval, err
}
func (ctrl AutomationScript) loadRecordScriptRunningCombo(ctx context.Context, namespaceID, moduleID, recordID uint64, record json.RawMessage) (ns *types.Namespace, m *types.Module, r *types.Record, err error) {
r = &types.Record{}
// Load requested namespace
if ns, err = ctrl.namespace.With(ctx).FindByID(namespaceID); err != nil {
return
}
if moduleID > 0 {
// Unmarshal given module or find existing one from ID
if m, err = ctrl.module.With(ctx).FindByID(ns.ID, moduleID); err != nil {
return
}
}
// Unmarshal given record or find existing one from ID
if record != nil {
if err = json.Unmarshal(record, &r); err != nil {
err = errors.New("Could not parse record payload")
return
}
} else if r, err = ctrl.record.With(ctx).FindByID(ns.ID, recordID); err != nil {
return
}
return
}
func (ctrl AutomationScript) makePayload(ctx context.Context, s *automation.Script, err error) (*automationScriptPayload, error) {
if err != nil || s == nil {
return nil, err
}
return &automationScriptPayload{
Script: s,
CanGrant: ctrl.ac.CanGrant(ctx),
CanUpdate: ctrl.ac.CanUpdateAutomationScript(ctx, s),
CanDelete: ctrl.ac.CanDeleteAutomationScript(ctx, s),
CanSetRunner: ctrl.ac.CanGrant(ctx),
CanSetAsCritical: true,
CanSetAsAsync: true,
}, nil
}
func (ctrl AutomationScript) makeFilterPayload(ctx context.Context, nn automation.ScriptSet, f automation.ScriptFilter, err error) (*automationScriptSetPayload, error) {
if err != nil {
return nil, err
}
modp := &automationScriptSetPayload{Filter: f, Set: make([]*automationScriptPayload, len(nn))}
for i := range nn {
modp.Set[i], _ = ctrl.makePayload(ctx, nn[i], nil)
}
return modp, nil
}