Automation script exp/imp, tweaking compose exporters
This commit is contained in:
@@ -17,7 +17,9 @@ import (
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/internal/auth"
|
||||
"github.com/cortezaproject/corteza-server/internal/permissions"
|
||||
"github.com/cortezaproject/corteza-server/pkg/automation"
|
||||
"github.com/cortezaproject/corteza-server/pkg/cli"
|
||||
"github.com/cortezaproject/corteza-server/pkg/deinterfacer"
|
||||
"github.com/cortezaproject/corteza-server/pkg/handle"
|
||||
sysTypes "github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
@@ -68,6 +70,16 @@ func Exporter(ctx context.Context, c *cli.Config) *cobra.Command {
|
||||
charts, _, err := service.DefaultChart.Find(types.ChartFilter{NamespaceID: ns.ID})
|
||||
cli.HandleError(err)
|
||||
|
||||
scripts, _, err := service.DefaultInternalAutomationManager.FindScripts(ctx, automation.ScriptFilter{})
|
||||
cli.HandleError(err)
|
||||
|
||||
triggers, _, err := service.DefaultInternalAutomationManager.FindTriggers(ctx, automation.TriggerFilter{})
|
||||
cli.HandleError(err)
|
||||
|
||||
scripts, _ = scripts.Filter(func(script *automation.Script) (b bool, e error) {
|
||||
return script.NamespaceID == ns.ID, nil
|
||||
})
|
||||
|
||||
y := yaml.NewEncoder(cmd.OutOrStdout())
|
||||
|
||||
nsOut.Name = ns.Name
|
||||
@@ -85,7 +97,9 @@ func Exporter(ctx context.Context, c *cli.Config) *cobra.Command {
|
||||
case "chart", "charts":
|
||||
nsOut.Charts = expCharts(charts, modules)
|
||||
case "page", "pages":
|
||||
nsOut.Pages = expPages(0, pages, modules, charts)
|
||||
nsOut.Pages = expPages(0, pages, modules, charts, scripts)
|
||||
case "scripts", "triggers", "automation":
|
||||
nsOut.Scripts = expAutomation(scripts, triggers, modules)
|
||||
case "allow", "deny", "permission", "permissions":
|
||||
out.Allow = expServicePermissions(permissions.Allow)
|
||||
out.Deny = expServicePermissions(permissions.Deny)
|
||||
@@ -124,6 +138,7 @@ type (
|
||||
Modules map[string]Module `yaml:",omitempty"`
|
||||
Pages map[string]Page `yaml:",omitempty"`
|
||||
Charts map[string]Chart `yaml:",omitempty"`
|
||||
Scripts map[string]Script `yaml:",omitempty"`
|
||||
|
||||
Allow map[string][]string `yaml:",omitempty"`
|
||||
Deny map[string][]string `yaml:",omitempty"`
|
||||
@@ -178,17 +193,36 @@ type (
|
||||
Deny map[string][]string `yaml:",omitempty"`
|
||||
}
|
||||
|
||||
ChartConfig struct {
|
||||
Reports []*ChartConfigReport
|
||||
Script struct {
|
||||
Source string `yaml:"source"`
|
||||
Async bool `yaml:"async"`
|
||||
RunInUA bool `yaml:"runInUA"`
|
||||
Critical bool `yaml:"critical"`
|
||||
Enabled bool `yaml:"enabled"`
|
||||
Timeout uint `yaml:"timeout"`
|
||||
|
||||
Triggers []map[string]interface{} `yaml:"triggers,omitempty"`
|
||||
|
||||
Allow map[string][]string `yaml:",omitempty"`
|
||||
Deny map[string][]string `yaml:",omitempty"`
|
||||
}
|
||||
ChartConfigReport struct {
|
||||
types.ChartConfigReport
|
||||
Module string `json:"module"`
|
||||
|
||||
ChartConfig struct {
|
||||
Reports []map[string]interface{}
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// preloaded roles so we can
|
||||
//
|
||||
roles sysTypes.RoleSet
|
||||
|
||||
// list of used page handles
|
||||
// we're exporting pages in a tree structure
|
||||
// so we need this to know if we've used a handle before
|
||||
//
|
||||
// non-autogenerated handles should not have this problem
|
||||
pagesHandles = make(map[string]bool)
|
||||
)
|
||||
|
||||
func expModules(mm types.ModuleSet) (o map[string]Module) {
|
||||
@@ -310,7 +344,7 @@ func expModuleFieldOptions(f *types.ModuleField, modules types.ModuleSet) types.
|
||||
return out
|
||||
}
|
||||
|
||||
func expPages(parentID uint64, pages types.PageSet, modules types.ModuleSet, charts types.ChartSet) (o map[string]Page) {
|
||||
func expPages(parentID uint64, pages types.PageSet, modules types.ModuleSet, charts types.ChartSet, scripts automation.ScriptSet) (o map[string]Page) {
|
||||
var (
|
||||
children = pages.FindByParent(parentID)
|
||||
handle string
|
||||
@@ -321,8 +355,8 @@ func expPages(parentID uint64, pages types.PageSet, modules types.ModuleSet, cha
|
||||
page := Page{
|
||||
Title: child.Title,
|
||||
Description: child.Description,
|
||||
Blocks: expPageBlocks(child.Blocks, pages, modules, charts),
|
||||
Pages: expPages(child.ID, pages, modules, charts),
|
||||
Blocks: expPageBlocks(child.Blocks, pages, modules, charts, scripts),
|
||||
Pages: expPages(child.ID, pages, modules, charts, scripts),
|
||||
Visible: child.Visible,
|
||||
|
||||
Allow: expResourcePermissions(permissions.Allow, types.PagePermissionResource),
|
||||
@@ -335,23 +369,24 @@ func expPages(parentID uint64, pages types.PageSet, modules types.ModuleSet, cha
|
||||
page.Module = fmt.Sprintf("Error: module with ID %d does not exist", child.ModuleID)
|
||||
} else {
|
||||
page.Module = makeHandleFromName(m.Name, m.Handle, "module-%d", child.ModuleID)
|
||||
|
||||
if child.Handle == "" {
|
||||
// Reuse module's handle for page
|
||||
handle = makeHandleFromName(m.Name, m.Handle, "record-page-%d", child.ModuleID)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
handle = makeHandleFromName(child.Title, child.Handle, "page-%d", child.ID)
|
||||
}
|
||||
|
||||
handle = makeHandleFromName(child.Title, child.Handle, "page-%d", child.ID)
|
||||
if handle == "" || pagesHandles[handle] {
|
||||
// if handle exists, force simple handle with id
|
||||
handle = makeHandleFromName("", "", "page-%d", child.ID)
|
||||
}
|
||||
|
||||
pagesHandles[handle] = true
|
||||
|
||||
o[handle] = page
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func expPageBlocks(in types.PageBlocks, pages types.PageSet, modules types.ModuleSet, charts types.ChartSet) types.PageBlocks {
|
||||
func expPageBlocks(in types.PageBlocks, pages types.PageSet, modules types.ModuleSet, charts types.ChartSet, scripts automation.ScriptSet) types.PageBlocks {
|
||||
out := types.PageBlocks(in)
|
||||
|
||||
// Remove extra options to keep the output tidy
|
||||
@@ -414,6 +449,31 @@ func expPageBlocks(in types.PageBlocks, pages types.PageSet, modules types.Modul
|
||||
rmFalse(out[i].Options, "hideSearch")
|
||||
rmFalse(out[i].Options, "hideSorting")
|
||||
rmFalse(out[i].Options, "allowExport")
|
||||
|
||||
if out[i].Kind == "Automation" {
|
||||
bb := make([]interface{}, 0)
|
||||
_ = deinterfacer.Each(out[i].Options["buttons"], func(_ int, _ string, btn interface{}) error {
|
||||
button := map[string]interface{}{}
|
||||
|
||||
_ = deinterfacer.Each(btn, func(_ int, k string, v interface{}) error {
|
||||
switch k {
|
||||
case "triggerID", "scriptID":
|
||||
if s := scripts.FindByID(deinterfacer.ToUint64(v)); s != nil {
|
||||
button["script"] = makeHandleFromName(s.Name, "", "automation-script-%d", s.ID)
|
||||
}
|
||||
default:
|
||||
button[k] = v
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
bb = append(bb, button)
|
||||
return nil
|
||||
})
|
||||
|
||||
out[i].Options["buttons"] = bb
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
@@ -425,24 +485,30 @@ func expCharts(charts types.ChartSet, modules types.ModuleSet) (o map[string]Cha
|
||||
for _, c := range charts {
|
||||
chart := Chart{
|
||||
Name: c.Name,
|
||||
Config: ChartConfig{Reports: make([]*ChartConfigReport, len(c.Config.Reports))},
|
||||
Config: ChartConfig{Reports: make([]map[string]interface{}, len(c.Config.Reports))},
|
||||
|
||||
Allow: expResourcePermissions(permissions.Allow, types.ChartPermissionResource),
|
||||
Deny: expResourcePermissions(permissions.Deny, types.ChartPermissionResource),
|
||||
}
|
||||
|
||||
for i, r := range c.Config.Reports {
|
||||
chart.Config.Reports[i] = &ChartConfigReport{
|
||||
ChartConfigReport: *r,
|
||||
if len(r.Metrics) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
rOut := map[string]interface{}{
|
||||
"filter": r.Filter,
|
||||
"metrics": r.Metrics,
|
||||
"dimensions": r.Dimensions,
|
||||
"renderer": r.Renderer,
|
||||
}
|
||||
|
||||
if r.ModuleID > 0 {
|
||||
module := modules.FindByID(r.ModuleID)
|
||||
chart.Config.Reports[i].ModuleID = 0
|
||||
chart.Config.Reports[i].Module =
|
||||
makeHandleFromName(module.Name, module.Handle, "module-%d", module.ID)
|
||||
rOut["module"] = makeHandleFromName(module.Name, module.Handle, "module-%d", module.ID)
|
||||
}
|
||||
|
||||
chart.Config.Reports[i] = rOut
|
||||
}
|
||||
|
||||
handle := makeHandleFromName(c.Name, c.Handle, "chart-%d", c.ID)
|
||||
@@ -453,6 +519,78 @@ func expCharts(charts types.ChartSet, modules types.ModuleSet) (o map[string]Cha
|
||||
return
|
||||
}
|
||||
|
||||
func expAutomation(ss automation.ScriptSet, tt automation.TriggerSet, mm types.ModuleSet) map[string]Script {
|
||||
var (
|
||||
script Script
|
||||
out = map[string]Script{}
|
||||
)
|
||||
|
||||
_ = ss.Walk(func(s *automation.Script) error {
|
||||
script = Script{
|
||||
Source: strings.TrimSpace(s.Source),
|
||||
Async: s.Async,
|
||||
RunInUA: s.RunInUA,
|
||||
Critical: s.Critical,
|
||||
Enabled: s.Enabled,
|
||||
Timeout: s.Timeout,
|
||||
|
||||
// ignoring run-as, we do not have support for user exporting
|
||||
// this will be solved when a.scripts are migrated to syste,
|
||||
|
||||
Triggers: []map[string]interface{}{},
|
||||
|
||||
Allow: expResourcePermissions(permissions.Allow, types.AutomationScriptPermissionResource),
|
||||
Deny: expResourcePermissions(permissions.Deny, types.AutomationScriptPermissionResource),
|
||||
}
|
||||
|
||||
handle := makeHandleFromName(s.Name, "", "automation-script-%d", s.ID)
|
||||
|
||||
tt.Walk(func(t *automation.Trigger) error {
|
||||
if t.ScriptID != s.ID {
|
||||
return nil
|
||||
}
|
||||
|
||||
trigger := map[string]interface{}{
|
||||
"resource": t.Resource,
|
||||
"event": t.Event,
|
||||
}
|
||||
|
||||
switch t.Event {
|
||||
case "beforeCreate", "beforeUpdate", "beforeDelete",
|
||||
"afterCreate", "afterUpdate", "afterDelete":
|
||||
moduleID := t.Uint64Condition()
|
||||
|
||||
if moduleID == 0 {
|
||||
return nil
|
||||
}
|
||||
module := mm.FindByID(moduleID)
|
||||
if module == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
trigger["module"] = makeHandleFromName(module.Name, module.Handle, "module-%d", module.ID)
|
||||
|
||||
case "interval", "deferred":
|
||||
trigger["condition"] = t.Condition
|
||||
|
||||
}
|
||||
|
||||
if !t.Enabled {
|
||||
trigger["enabled"] = false
|
||||
}
|
||||
|
||||
script.Triggers = append(script.Triggers, trigger)
|
||||
return nil
|
||||
})
|
||||
|
||||
out[handle] = script
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func expServicePermissions(access permissions.Access) map[string]map[string][]string {
|
||||
var (
|
||||
has bool
|
||||
@@ -509,9 +647,12 @@ func expResourcePermissions(access permissions.Access, resource permissions.Reso
|
||||
continue
|
||||
}
|
||||
|
||||
if rule.Access != access {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, has = sp[r.Handle]; !has {
|
||||
sp[r.Handle] = make([]string, 0)
|
||||
|
||||
}
|
||||
|
||||
sp[r.Handle] = append(sp[r.Handle], rule.Operation.String())
|
||||
|
||||
@@ -35,14 +35,16 @@ func Importer(ctx context.Context, c *cli.Config) *cobra.Command {
|
||||
err error
|
||||
)
|
||||
|
||||
if namespaceID, _ := strconv.ParseUint(nsFlag, 10, 64); namespaceID > 0 {
|
||||
ns, err = service.DefaultNamespace.FindByID(namespaceID)
|
||||
if err != repository.ErrNamespaceNotFound {
|
||||
cli.HandleError(err)
|
||||
}
|
||||
} else if ns, err = service.DefaultNamespace.FindByHandle(nsFlag); err != nil {
|
||||
if err != repository.ErrNamespaceNotFound {
|
||||
cli.HandleError(err)
|
||||
if nsFlag != "" {
|
||||
if namespaceID, _ := strconv.ParseUint(nsFlag, 10, 64); namespaceID > 0 {
|
||||
ns, err = service.DefaultNamespace.FindByID(namespaceID)
|
||||
if err != repository.ErrNamespaceNotFound {
|
||||
cli.HandleError(err)
|
||||
}
|
||||
} else if ns, err = service.DefaultNamespace.FindByHandle(nsFlag); err != nil {
|
||||
if err != repository.ErrNamespaceNotFound {
|
||||
cli.HandleError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,19 +67,19 @@ func Importer(ctx context.Context, c *cli.Config) *cobra.Command {
|
||||
service.DefaultModule.With(ctx),
|
||||
service.DefaultChart.With(ctx),
|
||||
service.DefaultPage.With(ctx),
|
||||
service.DefaultInternalAutomationManager,
|
||||
permissions.NewImporter(service.DefaultAccessControl.Whitelist()),
|
||||
)
|
||||
|
||||
for i, f := range ff {
|
||||
cmd.Printf("Importing from %s\n", args[i])
|
||||
|
||||
if err = yaml.NewDecoder(f).Decode(&aux); err != nil {
|
||||
return
|
||||
}
|
||||
cli.HandleError(yaml.NewDecoder(f).Decode(&aux))
|
||||
|
||||
if ns != nil {
|
||||
// If we're importing with --namespace switch,
|
||||
// we're going to import all into one NS
|
||||
|
||||
cli.HandleError(imp.GetNamespaceImporter().Cast(ns.Slug, aux))
|
||||
} else {
|
||||
// importing one or more namespaces
|
||||
@@ -92,12 +94,13 @@ func Importer(ctx context.Context, c *cli.Config) *cobra.Command {
|
||||
service.DefaultModule.With(ctx),
|
||||
service.DefaultChart.With(ctx),
|
||||
service.DefaultPage.With(ctx),
|
||||
service.DefaultInternalAutomationManager,
|
||||
service.DefaultAccessControl,
|
||||
))
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().String("namespace", "crm", "Import into namespace (by ID or string)")
|
||||
cmd.Flags().String("namespace", "", "Import into namespace (by ID or string)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
248
compose/importer/automation.go
Normal file
248
compose/importer/automation.go
Normal file
@@ -0,0 +1,248 @@
|
||||
package importer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/automation"
|
||||
"github.com/cortezaproject/corteza-server/pkg/deinterfacer"
|
||||
"github.com/cortezaproject/corteza-server/pkg/importer"
|
||||
)
|
||||
|
||||
type (
|
||||
// AutomationScript provides very basic and immature automation script importer
|
||||
AutomationScript struct {
|
||||
imp *Importer
|
||||
namespace *types.Namespace
|
||||
set automation.ScriptSet
|
||||
dirty map[uint64]bool
|
||||
|
||||
triggers map[string]automation.TriggerSet
|
||||
|
||||
modRefs []automationTriggerModuleRef
|
||||
}
|
||||
|
||||
automationTriggerModuleRef struct {
|
||||
// automation handle, report index, module handle
|
||||
as string
|
||||
ri int
|
||||
mh string
|
||||
}
|
||||
|
||||
automationFinder interface {
|
||||
// automation finder does not have find by name...
|
||||
FindScripts(ctx context.Context, f automation.ScriptFilter) (automation.ScriptSet, automation.ScriptFilter, error)
|
||||
}
|
||||
)
|
||||
|
||||
func NewAutomationImporter(imp *Importer, ns *types.Namespace) *AutomationScript {
|
||||
out := &AutomationScript{
|
||||
imp: imp,
|
||||
namespace: ns,
|
||||
set: automation.ScriptSet{},
|
||||
triggers: make(map[string]automation.TriggerSet),
|
||||
modRefs: make([]automationTriggerModuleRef, 0),
|
||||
dirty: make(map[uint64]bool),
|
||||
}
|
||||
|
||||
if imp.automationFinder != nil && ns.ID > 0 {
|
||||
out.set, _, _ = imp.automationFinder.FindScripts(
|
||||
context.Background(),
|
||||
automation.ScriptFilter{
|
||||
NamespaceID: ns.ID,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (asImp *AutomationScript) getModule(handle string) (*types.Module, error) {
|
||||
if g, ok := asImp.imp.namespaces.modules[asImp.namespace.Slug]; !ok {
|
||||
return nil, errors.Errorf("could not get modules %q from non existing namespace %q", handle, asImp.namespace.Slug)
|
||||
} else {
|
||||
return g.Get(handle)
|
||||
}
|
||||
}
|
||||
|
||||
func (asImp *AutomationScript) CastSet(set interface{}) error {
|
||||
return deinterfacer.Each(set, func(index int, handle string, def interface{}) error {
|
||||
if index > -1 {
|
||||
// Automation scripts defined as collection
|
||||
deinterfacer.KVsetString(&handle, "name", def)
|
||||
}
|
||||
|
||||
return asImp.Cast(handle, def)
|
||||
})
|
||||
}
|
||||
|
||||
func (asImp *AutomationScript) Cast(handle string, def interface{}) (err error) {
|
||||
if !deinterfacer.IsMap(def) {
|
||||
return errors.New("expecting map of values for automation")
|
||||
}
|
||||
|
||||
var script *automation.Script
|
||||
|
||||
if !importer.IsValidHandle(handle) {
|
||||
return errors.New("invalid automation handle")
|
||||
}
|
||||
|
||||
handle = importer.NormalizeHandle(handle)
|
||||
if script, err = asImp.Get(handle); err != nil {
|
||||
return err
|
||||
} else if script == nil {
|
||||
script = &automation.Script{
|
||||
NamespaceID: asImp.namespace.ID,
|
||||
Name: handle,
|
||||
}
|
||||
|
||||
asImp.set = append(asImp.set, script)
|
||||
} else if script.ID == 0 {
|
||||
return errors.Errorf("automation handle %q already defined in this import session", script.Name)
|
||||
}
|
||||
|
||||
asImp.dirty[script.ID] = true
|
||||
|
||||
return deinterfacer.Each(def, func(_ int, key string, val interface{}) (err error) {
|
||||
switch key {
|
||||
case "name":
|
||||
script.Name = deinterfacer.ToString(val)
|
||||
|
||||
case "source":
|
||||
script.Source = deinterfacer.ToString(val)
|
||||
|
||||
case "async":
|
||||
script.Async = deinterfacer.ToBool(val, false)
|
||||
case "runInUA":
|
||||
script.RunInUA = deinterfacer.ToBool(val, false)
|
||||
case "critical":
|
||||
script.Critical = deinterfacer.ToBool(val, false)
|
||||
case "enabled":
|
||||
script.Enabled = deinterfacer.ToBool(val, true)
|
||||
case "timeout":
|
||||
script.Timeout = uint(deinterfacer.ToInt(val))
|
||||
|
||||
case "triggers":
|
||||
asImp.triggers[handle], err = asImp.castTriggers(handle, script, val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case "allow", "deny":
|
||||
return asImp.imp.permissions.CastSet(types.AutomationScriptPermissionResource.String()+handle, key, val)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unexpected key %q for automation %q", key, handle)
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
func (asImp *AutomationScript) castTriggers(handle string, script *automation.Script, def interface{}) (automation.TriggerSet, error) {
|
||||
var (
|
||||
t *automation.Trigger
|
||||
tt = automation.TriggerSet{}
|
||||
)
|
||||
|
||||
return tt, deinterfacer.Each(def, func(n int, _ string, def interface{}) (err error) {
|
||||
t = &automation.Trigger{}
|
||||
err = deinterfacer.Each(def, func(_ int, key string, val interface{}) (err error) {
|
||||
switch key {
|
||||
case "enabled":
|
||||
t.Enabled = deinterfacer.ToBool(val, true)
|
||||
case "event":
|
||||
t.Condition = deinterfacer.ToString(val)
|
||||
case "resource":
|
||||
t.Resource = deinterfacer.ToString(val)
|
||||
case "condition":
|
||||
t.Condition = deinterfacer.ToString(val)
|
||||
case "module":
|
||||
module := deinterfacer.ToString(val)
|
||||
if m, err := asImp.getModule(module); err != nil || m == nil {
|
||||
return fmt.Errorf("unknown module %q referenced from automation script's %q trigger", module, handle)
|
||||
}
|
||||
asImp.modRefs = append(asImp.modRefs, automationTriggerModuleRef{handle, len(tt), module})
|
||||
default:
|
||||
return fmt.Errorf("unexpected key %q for automation script's %q trigger", key, handle)
|
||||
|
||||
}
|
||||
return
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
tt = append(tt, t)
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// Get existing automation scripts
|
||||
func (asImp *AutomationScript) Get(handle string) (*automation.Script, error) {
|
||||
handle = importer.NormalizeHandle(handle)
|
||||
if !importer.IsValidHandle(handle) {
|
||||
return nil, errors.New("invalid automation script handle")
|
||||
}
|
||||
|
||||
fmt.Printf(" => %s (?%v)\n", handle, asImp.set.FindByName(handle, asImp.namespace.ID) != nil)
|
||||
return asImp.set.FindByName(handle, asImp.namespace.ID), nil
|
||||
}
|
||||
|
||||
func (asImp *AutomationScript) Store(ctx context.Context, k automationScriptKeeper) (err error) {
|
||||
if err = asImp.resolveRefs(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return asImp.set.Walk(func(s *automation.Script) (err error) {
|
||||
var name = s.Name
|
||||
|
||||
if s.ID == 0 {
|
||||
s.NamespaceID = asImp.namespace.ID
|
||||
err = k.CreateScript(ctx, s)
|
||||
} else if asImp.dirty[s.ID] {
|
||||
err = k.UpdateScript(ctx, s)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not save script %s (%d)", s.Name, s.ID)
|
||||
}
|
||||
|
||||
asImp.dirty[s.ID] = false
|
||||
asImp.imp.permissions.UpdateResources(types.AutomationScriptPermissionResource.String(), name, s.ID)
|
||||
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// Resolve all refs for this page (page module, inside block)
|
||||
func (asImp *AutomationScript) resolveRefs() error {
|
||||
for _, ref := range asImp.modRefs {
|
||||
s := asImp.set.FindByName(ref.as, asImp.namespace.ID)
|
||||
if s == nil {
|
||||
return errors.Errorf("invalid reference, unknown automation script (%v)", ref)
|
||||
}
|
||||
if _, has := asImp.triggers[ref.as]; !has {
|
||||
return errors.Errorf("invalid reference, triggers not initialized (%v)", ref)
|
||||
}
|
||||
if ref.ri > len(asImp.triggers[ref.as]) {
|
||||
return errors.Errorf("invalid reference, trigger index out of range (%v)", ref)
|
||||
}
|
||||
|
||||
if module, err := asImp.getModule(ref.mh); err != nil {
|
||||
return errors.Errorf("invalid reference, module loading error: %v", err)
|
||||
} else if module == nil {
|
||||
return errors.Errorf("invalid reference, unknown module (%v)", ref)
|
||||
} else {
|
||||
asImp.triggers[ref.as][ref.ri].Condition = strconv.FormatUint(module.ID, 10)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -16,8 +16,8 @@ type (
|
||||
imp *Importer
|
||||
namespace *types.Namespace
|
||||
set types.ChartSet
|
||||
|
||||
modRefs []chartModuleRef
|
||||
dirty map[uint64]bool
|
||||
modRefs []chartModuleRef
|
||||
}
|
||||
|
||||
chartModuleRef struct {
|
||||
@@ -27,17 +27,25 @@ type (
|
||||
mh string
|
||||
}
|
||||
|
||||
// @todo remove finder strategy, directly provide set of items
|
||||
chartFinder interface {
|
||||
FindByHandle(uint64, string) (*types.Chart, error)
|
||||
Find(filter types.ChartFilter) (set types.ChartSet, f types.ChartFilter, err error)
|
||||
}
|
||||
)
|
||||
|
||||
func NewChartImporter(imp *Importer, ns *types.Namespace) *Chart {
|
||||
return &Chart{
|
||||
out := &Chart{
|
||||
imp: imp,
|
||||
namespace: ns,
|
||||
set: types.ChartSet{},
|
||||
dirty: make(map[uint64]bool),
|
||||
}
|
||||
|
||||
if imp.chartFinder != nil && ns.ID > 0 {
|
||||
out.set, _, _ = imp.chartFinder.Find(types.ChartFilter{NamespaceID: ns.ID})
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (pImp *Chart) getModule(handle string) (*types.Module, error) {
|
||||
@@ -75,18 +83,23 @@ func (cImp *Chart) Cast(handle string, def interface{}) (err error) {
|
||||
}
|
||||
|
||||
handle = importer.NormalizeHandle(handle)
|
||||
if chart, err = cImp.GetOrMake(handle); err != nil {
|
||||
if chart, err = cImp.Get(handle); err != nil {
|
||||
return err
|
||||
} else if chart == nil {
|
||||
chart = &types.Chart{
|
||||
Handle: handle,
|
||||
Name: handle,
|
||||
}
|
||||
|
||||
cImp.set = append(cImp.set, chart)
|
||||
} else if chart.ID == 0 {
|
||||
return errors.Errorf("chart handle %q already defined in this import session", chart.Handle)
|
||||
} else {
|
||||
cImp.dirty[chart.ID] = true
|
||||
}
|
||||
|
||||
return deinterfacer.Each(def, func(_ int, key string, val interface{}) (err error) {
|
||||
switch key {
|
||||
case "namespace":
|
||||
// namespace value sanity check
|
||||
if deinterfacer.ToString(val, cImp.namespace.Slug) != cImp.namespace.Slug {
|
||||
return fmt.Errorf("explicitly set namespace on chart %q shadows inherited namespace", cImp.namespace.Slug)
|
||||
}
|
||||
|
||||
case "handle":
|
||||
// handle value sanity check
|
||||
if deinterfacer.ToString(val, handle) != handle {
|
||||
@@ -162,51 +175,6 @@ func (cImp *Chart) castConfigReports(chart *types.Chart, def interface{}) ([]*ty
|
||||
})
|
||||
}
|
||||
|
||||
func (cImp *Chart) Exists(handle string) bool {
|
||||
handle = importer.NormalizeHandle(handle)
|
||||
|
||||
var (
|
||||
chart *types.Chart
|
||||
err error
|
||||
)
|
||||
|
||||
chart = cImp.set.FindByHandle(handle)
|
||||
if chart != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if cImp.namespace.ID == 0 {
|
||||
// Assuming new namespace, nothing exists yet..
|
||||
return false
|
||||
}
|
||||
|
||||
if cImp.imp.chartFinder != nil {
|
||||
chart, err = cImp.imp.chartFinder.FindByHandle(cImp.namespace.ID, handle)
|
||||
if err == nil && chart != nil {
|
||||
cImp.set = append(cImp.set, chart)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Get finds or makes a new chart
|
||||
func (cImp *Chart) GetOrMake(handle string) (chart *types.Chart, err error) {
|
||||
if chart, err = cImp.Get(handle); err != nil {
|
||||
return nil, err
|
||||
} else if chart == nil {
|
||||
chart = &types.Chart{
|
||||
Handle: handle,
|
||||
Name: handle,
|
||||
}
|
||||
|
||||
cImp.set = append(cImp.set, chart)
|
||||
}
|
||||
|
||||
return chart, nil
|
||||
}
|
||||
|
||||
// Get existing charts
|
||||
func (cImp *Chart) Get(handle string) (*types.Chart, error) {
|
||||
handle = importer.NormalizeHandle(handle)
|
||||
@@ -214,11 +182,7 @@ func (cImp *Chart) Get(handle string) (*types.Chart, error) {
|
||||
return nil, errors.New("invalid chart handle")
|
||||
}
|
||||
|
||||
if cImp.Exists(handle) {
|
||||
return cImp.set.FindByHandle(handle), nil
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
return cImp.set.FindByHandle(handle), nil
|
||||
}
|
||||
|
||||
func (cImp *Chart) Store(ctx context.Context, k chartKeeper) (err error) {
|
||||
@@ -232,15 +196,15 @@ func (cImp *Chart) Store(ctx context.Context, k chartKeeper) (err error) {
|
||||
if chart.ID == 0 {
|
||||
chart.NamespaceID = cImp.namespace.ID
|
||||
chart, err = k.Create(chart)
|
||||
} else {
|
||||
} else if cImp.dirty[chart.ID] {
|
||||
chart, err = k.Update(chart)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// @todo update module ref for charts
|
||||
|
||||
cImp.dirty[chart.ID] = false
|
||||
cImp.imp.permissions.UpdateResources(types.ChartPermissionResource.String(), handle, chart.ID)
|
||||
|
||||
return
|
||||
|
||||
@@ -13,7 +13,8 @@ func TestChartImport_CastSet(t *testing.T) {
|
||||
|
||||
impFixTester(t, "chart_full_slice", func(t *testing.T, imp *Importer) {
|
||||
req := require.New(t)
|
||||
req.Len(imp.GetChartImporter("test").set, 2)
|
||||
req.NotNil(imp.GetChartImporter(ns.Slug))
|
||||
req.Len(imp.GetChartImporter(ns.Slug).set, 2)
|
||||
})
|
||||
|
||||
impFixTester(t,
|
||||
@@ -21,8 +22,8 @@ func TestChartImport_CastSet(t *testing.T) {
|
||||
errors.New(`unknown module "un_kno_wn" referenced from chart "chart1" report config`))
|
||||
|
||||
// Pre fill with module that imported chart is referring to
|
||||
// imp.namespaces.modules[ns.Slug] = &Module{set: []*types.Module{{Handle: "foo"}}}
|
||||
modules.set = []*types.Module{{NamespaceID: ns.ID, Handle: "foo"}}
|
||||
imp.namespaces.Setup(ns)
|
||||
imp.GetModuleImporter(ns.Slug).set = types.ModuleSet{{NamespaceID: ns.ID, Handle: "foo"}}
|
||||
|
||||
impFixTester(t, "chart_full", func(t *testing.T, chart *Chart) {
|
||||
req := require.New(t)
|
||||
|
||||
@@ -6,20 +6,24 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/compose/service"
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/internal/permissions"
|
||||
"github.com/cortezaproject/corteza-server/pkg/automation"
|
||||
"github.com/cortezaproject/corteza-server/pkg/deinterfacer"
|
||||
"github.com/cortezaproject/corteza-server/pkg/importer"
|
||||
sysTypes "github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
|
||||
type (
|
||||
Importer struct {
|
||||
namespaces *Namespace
|
||||
|
||||
namespaceFinder namespaceFinder
|
||||
moduleFinder moduleFinder
|
||||
chartFinder chartFinder
|
||||
pageFinder pageFinder
|
||||
namespaceFinder namespaceFinder
|
||||
moduleFinder moduleFinder
|
||||
chartFinder chartFinder
|
||||
pageFinder pageFinder
|
||||
automationFinder automationFinder
|
||||
|
||||
permissions importer.PermissionImporter
|
||||
}
|
||||
@@ -38,14 +42,20 @@ type (
|
||||
Update(*types.Page) (*types.Page, error)
|
||||
Create(*types.Page) (*types.Page, error)
|
||||
}
|
||||
|
||||
automationScriptKeeper interface {
|
||||
UpdateScript(context.Context, *automation.Script) error
|
||||
CreateScript(context.Context, *automation.Script) error
|
||||
}
|
||||
)
|
||||
|
||||
func NewImporter(nsf namespaceFinder, mf moduleFinder, cf chartFinder, pf pageFinder, p importer.PermissionImporter) *Importer {
|
||||
func NewImporter(nsf namespaceFinder, mf moduleFinder, cf chartFinder, pf pageFinder, af automationFinder, p importer.PermissionImporter) *Importer {
|
||||
imp := &Importer{
|
||||
namespaceFinder: nsf,
|
||||
moduleFinder: mf,
|
||||
chartFinder: cf,
|
||||
pageFinder: pf,
|
||||
namespaceFinder: nsf,
|
||||
moduleFinder: mf,
|
||||
chartFinder: cf,
|
||||
pageFinder: pf,
|
||||
automationFinder: af,
|
||||
|
||||
permissions: p,
|
||||
}
|
||||
@@ -90,12 +100,22 @@ func (imp *Importer) Cast(in interface{}) (err error) {
|
||||
})
|
||||
}
|
||||
|
||||
func (imp *Importer) Store(ctx context.Context, nsStore namespaceKeeper, mStore moduleKeeper, cStore chartKeeper, pStore pageKeeper, pk permissions.ImportKeeper) (err error) {
|
||||
err = imp.namespaces.Store(ctx, nsStore, mStore, cStore, pStore)
|
||||
func (imp *Importer) Store(ctx context.Context, nsStore namespaceKeeper, mStore moduleKeeper, cStore chartKeeper, pStore pageKeeper, asStore automationScriptKeeper, pk permissions.ImportKeeper) (err error) {
|
||||
err = imp.namespaces.Store(ctx, nsStore, mStore, cStore, pStore, asStore)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not import namespaces")
|
||||
}
|
||||
|
||||
// Make sure we properly replace role handles with IDs
|
||||
if roles, err := service.DefaultSystemRole.Find(ctx); err != nil {
|
||||
return err
|
||||
} else {
|
||||
roles.Walk(func(role *sysTypes.Role) error {
|
||||
imp.permissions.UpdateRoles(role.Handle, role.ID)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
err = imp.permissions.Store(ctx, pk)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not import permissions")
|
||||
|
||||
@@ -8,93 +8,44 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/compose/repository"
|
||||
"github.com/cortezaproject/corteza-server/compose/service"
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/internal/permissions"
|
||||
)
|
||||
|
||||
type (
|
||||
namespaceMock struct{ set types.NamespaceSet }
|
||||
moduleMock struct{ set types.ModuleSet }
|
||||
chartMock struct{ set types.ChartSet }
|
||||
pageMock struct{ set types.PageSet }
|
||||
)
|
||||
|
||||
var (
|
||||
ns = &types.Namespace{
|
||||
ID: 1000000,
|
||||
Name: "Test",
|
||||
Slug: "test",
|
||||
Slug: "testing",
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
// Add namespace to the stack, make sure importer can find it
|
||||
namespaces = &namespaceMock{set: types.NamespaceSet{ns}}
|
||||
modules = &moduleMock{}
|
||||
charts = &chartMock{}
|
||||
pages = &pageMock{}
|
||||
pi *permissions.Importer
|
||||
|
||||
// whitelist = nil, anything can be added
|
||||
pi = permissions.NewImporter(service.AccessControl(nil).Whitelist())
|
||||
|
||||
imp = NewImporter(namespaces, modules, charts, pages, pi)
|
||||
imp *Importer
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
resetMocks()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func (mock *namespaceMock) FindByHandle(slug string) (o *types.Namespace, err error) {
|
||||
oo, err := mock.set.Filter(func(o *types.Namespace) (b bool, e error) {
|
||||
return o.Slug == slug, nil
|
||||
})
|
||||
func resetMocks() {
|
||||
// whitelist = nil, anything can be added
|
||||
pi = permissions.NewImporter(service.AccessControl(nil).Whitelist())
|
||||
|
||||
if len(oo) > 0 {
|
||||
return oo[0], nil
|
||||
} else {
|
||||
return nil, repository.ErrNamespaceNotFound
|
||||
}
|
||||
}
|
||||
imp = NewImporter(nil, nil, nil, nil, nil, pi)
|
||||
|
||||
func (mock *moduleMock) FindByHandle(namespaceID uint64, handle string) (o *types.Module, err error) {
|
||||
oo, err := mock.set.Filter(func(o *types.Module) (b bool, e error) {
|
||||
return o.Handle == handle && o.NamespaceID == namespaceID, nil
|
||||
})
|
||||
|
||||
if len(oo) > 0 {
|
||||
return oo[0], nil
|
||||
} else {
|
||||
return nil, repository.ErrModuleNotFound
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *chartMock) FindByHandle(namespaceID uint64, handle string) (o *types.Chart, err error) {
|
||||
oo, err := mock.set.Filter(func(o *types.Chart) (b bool, e error) {
|
||||
return o.Handle == handle && o.NamespaceID == namespaceID, nil
|
||||
})
|
||||
|
||||
if len(oo) > 0 {
|
||||
return oo[0], nil
|
||||
} else {
|
||||
return nil, repository.ErrChartNotFound
|
||||
}
|
||||
}
|
||||
|
||||
func (mock *pageMock) FindByHandle(namespaceID uint64, handle string) (o *types.Page, err error) {
|
||||
oo, err := mock.set.Filter(func(o *types.Page) (b bool, e error) {
|
||||
return o.Handle == handle && o.NamespaceID == namespaceID, nil
|
||||
})
|
||||
|
||||
if len(oo) > 0 {
|
||||
return oo[0], nil
|
||||
} else {
|
||||
return nil, repository.ErrPageNotFound
|
||||
}
|
||||
}
|
||||
|
||||
func impFixTester(t *testing.T, name string, tester interface{}) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// We're not calling reset mocks BEFORE calling tester()
|
||||
// because we want to have an option to set it up as we want
|
||||
defer resetMocks()
|
||||
|
||||
var aux interface{}
|
||||
req := require.New(t)
|
||||
f, err := os.Open(fmt.Sprintf("testdata/%s.yaml", name))
|
||||
|
||||
@@ -19,19 +19,28 @@ type (
|
||||
imp *Importer
|
||||
namespace *types.Namespace
|
||||
set types.ModuleSet
|
||||
dirty map[uint64]bool
|
||||
}
|
||||
|
||||
// @todo remove finder strategy, directly provide set of items
|
||||
moduleFinder interface {
|
||||
FindByHandle(uint64, string) (*types.Module, error)
|
||||
Find(filter types.ModuleFilter) (set types.ModuleSet, f types.ModuleFilter, err error)
|
||||
}
|
||||
)
|
||||
|
||||
func NewModuleImporter(imp *Importer, ns *types.Namespace) *Module {
|
||||
return &Module{
|
||||
out := &Module{
|
||||
imp: imp,
|
||||
namespace: ns,
|
||||
set: types.ModuleSet{},
|
||||
dirty: make(map[uint64]bool),
|
||||
}
|
||||
|
||||
if imp.moduleFinder != nil && ns.ID > 0 {
|
||||
out.set, _, _ = imp.moduleFinder.Find(types.ModuleFilter{NamespaceID: ns.ID})
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (pImp *Module) getPageImporter() (*Page, error) {
|
||||
@@ -69,10 +78,21 @@ func (mImp *Module) Cast(handle string, def interface{}) (err error) {
|
||||
}
|
||||
|
||||
handle = importer.NormalizeHandle(handle)
|
||||
if module, err = mImp.GetOrMake(handle); err != nil {
|
||||
if module, err = mImp.Get(handle); err != nil {
|
||||
return err
|
||||
} else if module == nil {
|
||||
module = &types.Module{
|
||||
Handle: handle,
|
||||
Name: handle,
|
||||
}
|
||||
|
||||
mImp.set = append(mImp.set, module)
|
||||
} else if module.ID == 0 {
|
||||
return errors.Errorf("module handle %q already defined in this import session", module.Handle)
|
||||
}
|
||||
|
||||
mImp.dirty[module.ID] = true
|
||||
|
||||
return deinterfacer.Each(def, func(_ int, key string, val interface{}) (err error) {
|
||||
switch key {
|
||||
case "namespace":
|
||||
@@ -211,51 +231,6 @@ func (mImp *Module) castFieldOptions(field *types.ModuleField, def interface{})
|
||||
})
|
||||
}
|
||||
|
||||
func (mImp *Module) Exists(handle string) bool {
|
||||
handle = importer.NormalizeHandle(handle)
|
||||
|
||||
var (
|
||||
module *types.Module
|
||||
err error
|
||||
)
|
||||
|
||||
module = mImp.set.FindByHandle(handle)
|
||||
if module != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if mImp.namespace.ID == 0 {
|
||||
// Assuming new namespace, nothing exists yet..
|
||||
return false
|
||||
}
|
||||
|
||||
if mImp.imp.moduleFinder != nil {
|
||||
module, err = mImp.imp.moduleFinder.FindByHandle(mImp.namespace.ID, handle)
|
||||
if err == nil && module != nil {
|
||||
mImp.set = append(mImp.set, module)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Get finds or makes a new module
|
||||
func (mImp *Module) GetOrMake(handle string) (module *types.Module, err error) {
|
||||
if module, err = mImp.Get(handle); err != nil {
|
||||
return nil, err
|
||||
} else if module == nil {
|
||||
module = &types.Module{
|
||||
Handle: handle,
|
||||
Name: handle,
|
||||
}
|
||||
|
||||
mImp.set = append(mImp.set, module)
|
||||
}
|
||||
|
||||
return module, nil
|
||||
}
|
||||
|
||||
// Get existing modules
|
||||
func (mImp *Module) Get(handle string) (*types.Module, error) {
|
||||
handle = importer.NormalizeHandle(handle)
|
||||
@@ -263,11 +238,7 @@ func (mImp *Module) Get(handle string) (*types.Module, error) {
|
||||
return nil, errors.New("invalid module handle")
|
||||
}
|
||||
|
||||
if mImp.Exists(handle) {
|
||||
return mImp.set.FindByHandle(handle), nil
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
return mImp.set.FindByHandle(handle), nil
|
||||
}
|
||||
|
||||
func (mImp *Module) Store(ctx context.Context, k moduleKeeper) error {
|
||||
@@ -281,7 +252,7 @@ func (mImp *Module) Store(ctx context.Context, k moduleKeeper) error {
|
||||
if module.ID == 0 {
|
||||
module.NamespaceID = mImp.namespace.ID
|
||||
module, err = k.Create(module)
|
||||
} else {
|
||||
} else if mImp.dirty[module.ID] {
|
||||
module, err = k.Update(module)
|
||||
}
|
||||
|
||||
@@ -289,6 +260,7 @@ func (mImp *Module) Store(ctx context.Context, k moduleKeeper) error {
|
||||
return
|
||||
}
|
||||
|
||||
mImp.dirty[module.ID] = false
|
||||
mImp.imp.permissions.UpdateResources(types.ModulePermissionResource.String(), handle, module.ID)
|
||||
|
||||
err = module.Fields.Walk(func(f *types.ModuleField) error {
|
||||
|
||||
@@ -15,7 +15,8 @@ type (
|
||||
Namespace struct {
|
||||
imp *Importer
|
||||
|
||||
set types.NamespaceSet
|
||||
set types.NamespaceSet
|
||||
dirty map[uint64]bool
|
||||
|
||||
// modules per namespace
|
||||
modules map[string]*Module
|
||||
@@ -25,10 +26,14 @@ type (
|
||||
|
||||
// pages per namespace
|
||||
pages map[string]*Page
|
||||
|
||||
// pages per namespace
|
||||
scripts map[string]*AutomationScript
|
||||
}
|
||||
|
||||
// @todo remove finder strategy, directly provide set of items
|
||||
namespaceFinder interface {
|
||||
FindByHandle(string) (*types.Namespace, error)
|
||||
Find(filter types.NamespaceFilter) (set types.NamespaceSet, f types.NamespaceFilter, err error)
|
||||
}
|
||||
|
||||
namespaceKeeper interface {
|
||||
@@ -38,15 +43,23 @@ type (
|
||||
)
|
||||
|
||||
func NewNamespaceImporter(imp *Importer) *Namespace {
|
||||
return &Namespace{
|
||||
out := &Namespace{
|
||||
imp: imp,
|
||||
|
||||
set: types.NamespaceSet{},
|
||||
set: types.NamespaceSet{},
|
||||
dirty: make(map[uint64]bool),
|
||||
|
||||
modules: map[string]*Module{},
|
||||
charts: map[string]*Chart{},
|
||||
pages: map[string]*Page{},
|
||||
scripts: map[string]*AutomationScript{},
|
||||
}
|
||||
|
||||
if imp.namespaceFinder != nil {
|
||||
out.set, _, _ = imp.namespaceFinder.Find(types.NamespaceFilter{})
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// CastSet resolves permission rules:
|
||||
@@ -77,10 +90,23 @@ func (nsImp *Namespace) Cast(handle string, def interface{}) (err error) {
|
||||
}
|
||||
|
||||
handle = importer.NormalizeHandle(handle)
|
||||
|
||||
if namespace, err = nsImp.Get(handle); err != nil {
|
||||
return err
|
||||
} else if namespace == nil {
|
||||
namespace = &types.Namespace{
|
||||
Slug: handle,
|
||||
Name: handle,
|
||||
Enabled: true,
|
||||
}
|
||||
} else if namespace.ID == 0 {
|
||||
return errors.Errorf("namespace handle %q already defined in this import session", namespace.Slug)
|
||||
} else {
|
||||
nsImp.dirty[namespace.ID] = true
|
||||
}
|
||||
|
||||
nsImp.Setup(namespace)
|
||||
|
||||
return deinterfacer.Each(def, func(_ int, key string, val interface{}) (err error) {
|
||||
switch key {
|
||||
case "handle", "slug":
|
||||
@@ -94,6 +120,7 @@ func (nsImp *Namespace) Cast(handle string, def interface{}) (err error) {
|
||||
|
||||
case "meta":
|
||||
namespace.Meta, err = nsImp.castMeta(namespace, val)
|
||||
|
||||
return
|
||||
|
||||
case "modules":
|
||||
@@ -105,6 +132,9 @@ func (nsImp *Namespace) Cast(handle string, def interface{}) (err error) {
|
||||
case "pages":
|
||||
return nsImp.castPages(handle, val)
|
||||
|
||||
case "scripts":
|
||||
return nsImp.castScripts(handle, val)
|
||||
|
||||
case "allow", "deny":
|
||||
return nsImp.imp.permissions.CastSet(types.NamespacePermissionResource.String()+namespace.Slug, key, val)
|
||||
|
||||
@@ -162,28 +192,13 @@ func (nsImp *Namespace) castPages(handle string, def interface{}) error {
|
||||
return nsImp.pages[handle].CastSet(def)
|
||||
}
|
||||
|
||||
func (nsImp *Namespace) Exists(handle string) bool {
|
||||
handle = importer.NormalizeHandle(handle)
|
||||
func (nsImp *Namespace) castScripts(handle string, def interface{}) error {
|
||||
if nsImp.scripts[handle] == nil {
|
||||
return fmt.Errorf("unknown namespace %q", handle)
|
||||
|
||||
var (
|
||||
namespace *types.Namespace
|
||||
err error
|
||||
)
|
||||
|
||||
namespace = nsImp.set.FindByHandle(handle)
|
||||
if namespace != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if nsImp.imp.namespaceFinder != nil {
|
||||
namespace, err = nsImp.imp.namespaceFinder.FindByHandle(handle)
|
||||
if err == nil && namespace != nil {
|
||||
nsImp.set = append(nsImp.set, namespace)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return nsImp.scripts[handle].CastSet(def)
|
||||
}
|
||||
|
||||
// Get finds or creates a new namespace
|
||||
@@ -194,30 +209,27 @@ func (nsImp *Namespace) Get(handle string) (*types.Namespace, error) {
|
||||
return nil, errors.New("invalid namespace handle")
|
||||
}
|
||||
|
||||
if !nsImp.Exists(handle) {
|
||||
nsImp.set = append(nsImp.set, &types.Namespace{
|
||||
Slug: handle,
|
||||
Name: handle,
|
||||
Enabled: true,
|
||||
})
|
||||
}
|
||||
|
||||
namespace := nsImp.set.FindByHandle(handle)
|
||||
|
||||
nsImp.pages[handle] = NewPageImporter(nsImp.imp, namespace)
|
||||
nsImp.modules[handle] = NewModuleImporter(nsImp.imp, namespace)
|
||||
nsImp.charts[handle] = NewChartImporter(nsImp.imp, namespace)
|
||||
|
||||
return namespace, nil
|
||||
return nsImp.set.FindByHandle(handle), nil
|
||||
}
|
||||
|
||||
func (nsImp *Namespace) Store(ctx context.Context, nsk namespaceKeeper, mk moduleKeeper, ck chartKeeper, pk pageKeeper) error {
|
||||
func (nsImp *Namespace) Setup(namespace *types.Namespace) {
|
||||
nsImp.set = append(nsImp.set, namespace)
|
||||
|
||||
if _, has := nsImp.modules[namespace.Slug]; !has {
|
||||
nsImp.modules[namespace.Slug] = NewModuleImporter(nsImp.imp, namespace)
|
||||
nsImp.pages[namespace.Slug] = NewPageImporter(nsImp.imp, namespace)
|
||||
nsImp.charts[namespace.Slug] = NewChartImporter(nsImp.imp, namespace)
|
||||
nsImp.scripts[namespace.Slug] = NewAutomationImporter(nsImp.imp, namespace)
|
||||
}
|
||||
}
|
||||
|
||||
func (nsImp *Namespace) Store(ctx context.Context, nsk namespaceKeeper, mk moduleKeeper, ck chartKeeper, pk pageKeeper, sk automationScriptKeeper) error {
|
||||
return nsImp.set.Walk(func(namespace *types.Namespace) (err error) {
|
||||
var handle = namespace.Slug
|
||||
|
||||
if namespace.ID == 0 {
|
||||
namespace, err = nsk.Create(namespace)
|
||||
} else {
|
||||
} else if nsImp.dirty[namespace.ID] {
|
||||
namespace, err = nsk.Update(namespace)
|
||||
}
|
||||
|
||||
@@ -225,6 +237,7 @@ func (nsImp *Namespace) Store(ctx context.Context, nsk namespaceKeeper, mk modul
|
||||
return
|
||||
}
|
||||
|
||||
nsImp.dirty[namespace.ID] = false
|
||||
nsImp.imp.permissions.UpdateResources(types.NamespacePermissionResource.String(), handle, namespace.ID)
|
||||
|
||||
if _, ok := nsImp.modules[handle]; ok {
|
||||
@@ -232,15 +245,17 @@ func (nsImp *Namespace) Store(ctx context.Context, nsk namespaceKeeper, mk modul
|
||||
if err = nsImp.modules[handle].Store(ctx, mk); err != nil {
|
||||
return errors.Wrap(err, "could not import modules")
|
||||
}
|
||||
if err = nsImp.scripts[handle].Store(ctx, sk); err != nil {
|
||||
return errors.Wrap(err, "could not import automation scripts")
|
||||
}
|
||||
|
||||
}
|
||||
if err = nsImp.charts[handle].Store(ctx, ck); err != nil {
|
||||
return errors.Wrap(err, "could not import charts")
|
||||
}
|
||||
|
||||
if err = nsImp.charts[handle].Store(ctx, ck); err != nil {
|
||||
return errors.Wrap(err, "could not import charts")
|
||||
}
|
||||
|
||||
if err = nsImp.pages[handle].Store(ctx, pk); err != nil {
|
||||
return errors.Wrap(err, "could not import pages")
|
||||
if err = nsImp.pages[handle].Store(ctx, pk); err != nil {
|
||||
return errors.Wrap(err, "could not import pages")
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
@@ -3,11 +3,13 @@ package importer
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/cortezaproject/corteza-server/compose/types"
|
||||
"github.com/cortezaproject/corteza-server/pkg/automation"
|
||||
"github.com/cortezaproject/corteza-server/pkg/deinterfacer"
|
||||
"github.com/cortezaproject/corteza-server/pkg/importer"
|
||||
)
|
||||
@@ -18,6 +20,7 @@ type (
|
||||
|
||||
namespace *types.Namespace
|
||||
set types.PageSet
|
||||
dirty map[uint64]bool
|
||||
|
||||
// page => module handle
|
||||
modules map[string]string
|
||||
@@ -26,22 +29,30 @@ type (
|
||||
tree map[string][]string
|
||||
}
|
||||
|
||||
// @todo remove finder strategy, directly provide set of items
|
||||
pageFinder interface {
|
||||
FindByHandle(uint64, string) (*types.Page, error)
|
||||
Find(filter types.PageFilter) (set types.PageSet, f types.PageFilter, err error)
|
||||
}
|
||||
)
|
||||
|
||||
func NewPageImporter(imp *Importer, ns *types.Namespace) *Page {
|
||||
return &Page{
|
||||
out := &Page{
|
||||
imp: imp,
|
||||
|
||||
namespace: ns,
|
||||
|
||||
set: types.PageSet{},
|
||||
set: types.PageSet{},
|
||||
dirty: make(map[uint64]bool),
|
||||
|
||||
modules: map[string]string{},
|
||||
tree: map[string][]string{},
|
||||
}
|
||||
|
||||
if imp.pageFinder != nil && ns.ID > 0 {
|
||||
out.set, _, _ = imp.pageFinder.Find(types.PageFilter{NamespaceID: ns.ID})
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func (pImp *Page) getModule(handle string) (*types.Module, error) {
|
||||
@@ -52,6 +63,14 @@ func (pImp *Page) getModule(handle string) (*types.Module, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (pImp *Page) getScript(name string) (*automation.Script, error) {
|
||||
if g, ok := pImp.imp.namespaces.scripts[pImp.namespace.Slug]; !ok {
|
||||
return nil, errors.Errorf("could not get scripts %q from non existing namespace %q", name, pImp.namespace.Slug)
|
||||
} else {
|
||||
return g.Get(name)
|
||||
}
|
||||
}
|
||||
|
||||
func (pImp *Page) getChart(handle string) (*types.Chart, error) {
|
||||
if g, ok := pImp.imp.namespaces.charts[pImp.namespace.Slug]; !ok {
|
||||
return nil, errors.Errorf("could not get chart %q from non existing namespace %q", handle, pImp.namespace.Slug)
|
||||
@@ -93,12 +112,28 @@ func (pImp *Page) cast(parent, handle string, def interface{}) (err error) {
|
||||
}
|
||||
|
||||
handle = importer.NormalizeHandle(handle)
|
||||
if page, err = pImp.GetOrMake(handle); err != nil {
|
||||
|
||||
if page, err = pImp.Get(handle); err != nil {
|
||||
return err
|
||||
} else if page == nil {
|
||||
page = &types.Page{
|
||||
Handle: handle,
|
||||
Title: handle,
|
||||
Visible: true,
|
||||
}
|
||||
|
||||
pImp.set = append(pImp.set, page)
|
||||
} else if page.ID == 0 {
|
||||
return errors.Errorf("page handle %q already defined in this import session", page.Handle)
|
||||
} else {
|
||||
pImp.dirty[page.ID] = true
|
||||
}
|
||||
|
||||
pImp.tree[parent] = append(pImp.tree[parent], handle)
|
||||
|
||||
// Make pages are always sorted
|
||||
sort.Strings(pImp.tree[parent])
|
||||
|
||||
if title, ok := def.(string); ok && title != "" {
|
||||
page.Title = title
|
||||
return nil
|
||||
@@ -224,52 +259,6 @@ func (pImp *Page) castBlockStyle(page *types.Page, n int, def interface{}) (s ty
|
||||
})
|
||||
}
|
||||
|
||||
func (pImp *Page) Exists(handle string) bool {
|
||||
handle = importer.NormalizeHandle(handle)
|
||||
|
||||
var (
|
||||
page *types.Page
|
||||
err error
|
||||
)
|
||||
|
||||
page = pImp.set.FindByHandle(handle)
|
||||
if page != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if pImp.namespace.ID == 0 {
|
||||
// Assuming new namespace, nothing exists yet..
|
||||
return false
|
||||
}
|
||||
|
||||
if pImp.imp.pageFinder != nil {
|
||||
page, err = pImp.imp.pageFinder.FindByHandle(pImp.namespace.ID, handle)
|
||||
if err == nil && page != nil {
|
||||
pImp.set = append(pImp.set, page)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Get finds or makes a new page
|
||||
func (pImp *Page) GetOrMake(handle string) (page *types.Page, err error) {
|
||||
if page, err = pImp.Get(handle); err != nil {
|
||||
return nil, err
|
||||
} else if page == nil {
|
||||
page = &types.Page{
|
||||
Handle: handle,
|
||||
Title: handle,
|
||||
Visible: true,
|
||||
}
|
||||
|
||||
pImp.set = append(pImp.set, page)
|
||||
}
|
||||
|
||||
return page, nil
|
||||
}
|
||||
|
||||
// Get existing pages
|
||||
func (pImp *Page) Get(handle string) (*types.Page, error) {
|
||||
handle = importer.NormalizeHandle(handle)
|
||||
@@ -277,12 +266,9 @@ func (pImp *Page) Get(handle string) (*types.Page, error) {
|
||||
return nil, errors.New("invalid page handle")
|
||||
}
|
||||
|
||||
if pImp.Exists(handle) {
|
||||
return pImp.set.FindByHandle(handle), nil
|
||||
} else {
|
||||
return nil, nil
|
||||
}
|
||||
return pImp.set.FindByHandle(handle), nil
|
||||
}
|
||||
|
||||
func (pImp *Page) Store(ctx context.Context, k pageKeeper) error {
|
||||
return pImp.storeChildren(ctx, "", k)
|
||||
}
|
||||
@@ -308,7 +294,7 @@ func (pImp *Page) storeChildren(ctx context.Context, parent string, k pageKeeper
|
||||
if page.ID == 0 {
|
||||
page.NamespaceID = pImp.namespace.ID
|
||||
page, err = k.Create(page)
|
||||
} else {
|
||||
} else if pImp.dirty[page.ID] {
|
||||
page, err = k.Update(page)
|
||||
}
|
||||
|
||||
@@ -316,6 +302,7 @@ func (pImp *Page) storeChildren(ctx context.Context, parent string, k pageKeeper
|
||||
return
|
||||
}
|
||||
|
||||
pImp.dirty[page.ID] = false
|
||||
if page.Handle == "" {
|
||||
continue
|
||||
}
|
||||
@@ -335,6 +322,9 @@ func (pImp *Page) resolveRefs(page *types.Page) error {
|
||||
if moduleHandle, ok := pImp.modules[page.Handle]; ok {
|
||||
if module, err := pImp.getModule(moduleHandle); err != nil {
|
||||
return err
|
||||
} else if module == nil {
|
||||
return errors.Wrapf(err, "could not load module %q for page %q",
|
||||
moduleHandle, page.Handle)
|
||||
} else {
|
||||
page.ModuleID = module.ID
|
||||
}
|
||||
@@ -347,8 +337,8 @@ func (pImp *Page) resolveRefs(page *types.Page) error {
|
||||
|
||||
if h, ok := b.Options["module"]; ok {
|
||||
if refm, err := pImp.getModule(deinterfacer.ToString(h)); err != nil || refm == nil {
|
||||
return errors.Wrapf(err, "could not load module %q for page %q block #%d",
|
||||
h, page.Handle, i+1)
|
||||
return errors.Errorf("could not load module %q for page %q block #%d (%v)",
|
||||
h, page.Handle, i+1, err)
|
||||
} else {
|
||||
b.Options["moduleID"] = strconv.FormatUint(refm.ID, 10)
|
||||
delete(b.Options, "module")
|
||||
@@ -357,8 +347,8 @@ func (pImp *Page) resolveRefs(page *types.Page) error {
|
||||
|
||||
if h, ok := b.Options["page"]; ok {
|
||||
if refp, err := pImp.Get(deinterfacer.ToString(h)); err != nil || refp == nil {
|
||||
return errors.Wrapf(err, "could not load page %q for page %q block #%d",
|
||||
h, page.Handle, i+1)
|
||||
return errors.Errorf("could not load page %q for page %q block #%d (%v)",
|
||||
h, page.Handle, i+1, err)
|
||||
} else {
|
||||
b.Options["pageID"] = strconv.FormatUint(refp.ID, 10)
|
||||
delete(b.Options, "page")
|
||||
@@ -367,13 +357,49 @@ func (pImp *Page) resolveRefs(page *types.Page) error {
|
||||
|
||||
if h, ok := b.Options["chart"]; ok {
|
||||
if refc, err := pImp.getChart(deinterfacer.ToString(h)); err != nil || refc == nil {
|
||||
return errors.Wrapf(err, "could not load chart %q for page %q block #%d",
|
||||
h, page.Handle, i+1)
|
||||
return errors.Errorf("could not load chart %q for page %q block #%d (%v)",
|
||||
h, page.Handle, i+1, err)
|
||||
} else {
|
||||
b.Options["chartID"] = strconv.FormatUint(refc.ID, 10)
|
||||
delete(b.Options, "chart")
|
||||
}
|
||||
}
|
||||
|
||||
if b.Kind == "Automation" {
|
||||
bb := make([]interface{}, 0)
|
||||
err := deinterfacer.Each(b.Options["buttons"], func(_ int, _ string, btn interface{}) (err error) {
|
||||
button := map[string]interface{}{}
|
||||
|
||||
err = deinterfacer.Each(btn, func(_ int, k string, v interface{}) error {
|
||||
switch k {
|
||||
case "script":
|
||||
if s, err := pImp.getScript(deinterfacer.ToString(v)); err != nil || s == nil {
|
||||
return errors.Errorf("could not load script %q for page %q block #%d (%v)",
|
||||
v, page.Handle, i+1, err)
|
||||
} else {
|
||||
button["scriptID"] = s.ID
|
||||
}
|
||||
default:
|
||||
button[k] = v
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bb = append(bb, button)
|
||||
return nil
|
||||
})
|
||||
|
||||
b.Options["buttons"] = bb
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user