3
0

Add compose setting import/export

This commit is contained in:
Tomaž Jerman 2019-10-10 11:25:10 +02:00
parent b06266c68b
commit cb202d474d
7 changed files with 292 additions and 80 deletions

View File

@ -22,6 +22,8 @@ import (
"github.com/cortezaproject/corteza-server/pkg/deinterfacer"
"github.com/cortezaproject/corteza-server/pkg/handle"
"github.com/cortezaproject/corteza-server/pkg/permissions"
"github.com/cortezaproject/corteza-server/pkg/settings"
intSettings "github.com/cortezaproject/corteza-server/pkg/settings"
sysTypes "github.com/cortezaproject/corteza-server/system/types"
)
@ -39,105 +41,144 @@ func Exporter(ctx context.Context, c *cli.Config) *cobra.Command {
var (
nsFlag = cmd.Flags().Lookup("namespace").Value.String()
ns *types.Namespace
err error
sFlag = cmd.Flags().Lookup("settings").Changed
pFlag = cmd.Flags().Lookup("permissions").Changed
out = Compose{
out = &Compose{
Namespaces: map[string]Namespace{},
Settings: yaml.MapSlice{},
}
nsOut = Namespace{}
)
if nsFlag == "" {
cli.HandleError(errors.New("Specify namespace to export from"))
if nsFlag == "" && !sFlag && !pFlag {
cli.HandleError(errors.New("Specify namespace or setting or permissions flag"))
}
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 != "" {
nsExporter(ctx, out, nsFlag, args)
}
// roles, err = service.DefaultSystemRole.Find(ctx)
// cli.HandleError(err)
// At the moment, we can not load roles from system service
// so we'll just use static set of known roles
//
// Roles are use for resolving access control
roles = sysTypes.RoleSet{
&sysTypes.Role{ID: permissions.EveryoneRoleID, Handle: "everyone"},
&sysTypes.Role{ID: permissions.AdminsRoleID, Handle: "admins"},
if sFlag {
settingExporter(ctx, out)
}
modules, _, err := service.DefaultModule.Find(types.ModuleFilter{NamespaceID: ns.ID})
cli.HandleError(err)
pages, _, err := service.DefaultPage.Find(types.PageFilter{NamespaceID: ns.ID})
cli.HandleError(err)
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
})
if pFlag {
permissionExporter(ctx, out)
}
y := yaml.NewEncoder(cmd.OutOrStdout())
// nsOut.Name = ns.Name
// nsOut.Handle = ns.Slug
// nsOut.Enabled = ns.Enabled
// nsOut.Meta = ns.Meta
//
// nsOut.Allow = expResourcePermissions(permissions.Allow, ns.PermissionResource())
// nsOut.Deny = expResourcePermissions(permissions.Deny, ns.PermissionResource())
for _, arg := range args {
switch arg {
case "module", "modules":
nsOut.Modules = expModules(modules)
case "chart", "charts":
nsOut.Charts = expCharts(charts, modules)
case "page", "pages":
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)
}
}
// out.Namespaces[ns.Slug] = nsOut
nsOut.Namespace = ns.Slug
_, _ = y, out
cli.HandleError(y.Encode(nsOut))
cli.HandleError(y.Encode(out))
},
}
cmd.Flags().String("namespace", "", "Export namespace resources (by ID or string)")
cmd.Flags().BoolP("settings", "s", false, "Export settings")
cmd.Flags().BoolP("permissions", "p", false, "Export permissions")
return cmd
}
func nsExporter(ctx context.Context, out *Compose, nsFlag string, args []string) {
var (
ns *types.Namespace
err error
nsOut = Namespace{}
)
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)
}
}
// roles, err = service.DefaultSystemRole.Find(ctx)
// cli.HandleError(err)
// At the moment, we can not load roles from system service
// so we'll just use static set of known roles
//
// Roles are use for resolving access control
roles = sysTypes.RoleSet{
&sysTypes.Role{ID: permissions.EveryoneRoleID, Handle: "everyone"},
&sysTypes.Role{ID: permissions.AdminsRoleID, Handle: "admins"},
}
modules, _, err := service.DefaultModule.Find(types.ModuleFilter{NamespaceID: ns.ID})
cli.HandleError(err)
pages, _, err := service.DefaultPage.Find(types.PageFilter{NamespaceID: ns.ID})
cli.HandleError(err)
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
})
// nsOut.Name = ns.Name
// nsOut.Handle = ns.Slug
// nsOut.Enabled = ns.Enabled
// nsOut.Meta = ns.Meta
//
// nsOut.Allow = expResourcePermissions(permissions.Allow, ns.PermissionResource())
// nsOut.Deny = expResourcePermissions(permissions.Deny, ns.PermissionResource())
for _, arg := range args {
switch arg {
case "module", "modules":
nsOut.Modules = expModules(modules)
case "chart", "charts":
nsOut.Charts = expCharts(charts, modules)
case "page", "pages":
nsOut.Pages = expPages(0, pages, modules, charts, scripts)
case "scripts", "triggers", "automation":
nsOut.Scripts = expAutomation(scripts, triggers, modules)
}
}
nsOut.Namespace = ns.Slug
out.Namespaces[ns.Slug] = nsOut
}
func settingExporter(ctx context.Context, out *Compose) {
var (
err error
)
ss, err := service.DefaultSettings.FindByPrefix("")
cli.HandleError(err)
out.Settings = settings.Export(ss)
}
func permissionExporter(ctx context.Context, out *Compose) {
roles := sysTypes.RoleSet{
&sysTypes.Role{ID: permissions.EveryoneRoleID, Handle: "everyone"},
&sysTypes.Role{ID: permissions.AdminsRoleID, Handle: "admins"},
}
out.Allow = expServicePermissions(permissions.Allow)
out.Deny = expServicePermissions(permissions.Deny)
}
// This is PoC for exporting compose resources
//
type (
Compose struct {
Namespaces map[string]Namespace
Namespaces map[string]Namespace `yaml:",omitempty"`
Settings yaml.MapSlice `yaml:",omitempty"`
Allow map[string]map[string][]string `yaml:",omitempty"`
Deny map[string]map[string][]string `yaml:",omitempty"`
@ -242,6 +283,19 @@ var (
pagesHandles = make(map[string]bool)
)
func expSettings(ss intSettings.ValueSet) (o yaml.MapSlice) {
o = yaml.MapSlice{}
for _, s := range ss {
setting := yaml.MapItem{
Key: s.Name,
Value: s.Value.String(),
}
o = append(o, setting)
}
return o
}
func expModules(mm types.ModuleSet) (o map[string]Module) {
o = map[string]Module{}

View File

@ -9,6 +9,7 @@ import (
"github.com/cortezaproject/corteza-server/compose/service"
"github.com/cortezaproject/corteza-server/compose/types"
"github.com/cortezaproject/corteza-server/pkg/permissions"
"github.com/cortezaproject/corteza-server/pkg/settings"
sysTypes "github.com/cortezaproject/corteza-server/system/types"
)
@ -22,7 +23,9 @@ func Import(ctx context.Context, ns *types.Namespace, ff ...io.Reader) (err erro
service.DefaultChart.With(ctx),
service.DefaultPage.With(ctx),
service.DefaultInternalAutomationManager,
permissions.NewImporter(service.DefaultAccessControl.Whitelist()),
settings.NewImporter(),
)
// At the moment, we can not load roles from system service
@ -41,12 +44,12 @@ func Import(ctx context.Context, ns *types.Namespace, ff ...io.Reader) (err erro
}
if ns != nil {
// If we're importing with --namespace switch,
// we're going to import all into one NS
err = imp.GetNamespaceImporter().Cast(ns.Slug, aux)
if mp, ok := aux.(map[interface{}]interface{}); ok && mp["namespaces"] != nil {
err = imp.GetNamespaceImporter().Cast(ns.Slug, mp["namespaces"])
} else {
err = imp.GetNamespaceImporter().Cast(ns.Slug, aux)
}
} else {
// importing one or more namespaces
err = imp.Cast(aux)
}
@ -71,6 +74,7 @@ func Import(ctx context.Context, ns *types.Namespace, ff ...io.Reader) (err erro
service.DefaultRecord.With(ctx),
service.DefaultInternalAutomationManager,
service.DefaultAccessControl,
service.DefaultSettings,
roles,
)
}

View File

@ -11,6 +11,7 @@ import (
"github.com/cortezaproject/corteza-server/pkg/deinterfacer"
"github.com/cortezaproject/corteza-server/pkg/importer"
"github.com/cortezaproject/corteza-server/pkg/permissions"
"github.com/cortezaproject/corteza-server/pkg/settings"
sysTypes "github.com/cortezaproject/corteza-server/system/types"
)
@ -25,6 +26,7 @@ type (
automationFinder automationFinder
permissions importer.PermissionImporter
settings importer.SettingImporter
}
moduleKeeper interface {
@ -53,7 +55,7 @@ type (
}
)
func NewImporter(nsf namespaceFinder, mf moduleFinder, cf chartFinder, pf pageFinder, af automationFinder, p importer.PermissionImporter) *Importer {
func NewImporter(nsf namespaceFinder, mf moduleFinder, cf chartFinder, pf pageFinder, af automationFinder, p importer.PermissionImporter, s importer.SettingImporter) *Importer {
imp := &Importer{
namespaceFinder: nsf,
moduleFinder: mf,
@ -62,6 +64,7 @@ func NewImporter(nsf namespaceFinder, mf moduleFinder, cf chartFinder, pf pageFi
automationFinder: af,
permissions: p,
settings: s,
}
imp.namespaces = NewNamespaceImporter(imp)
@ -110,6 +113,9 @@ func (imp *Importer) Cast(def interface{}) (err error) {
case "namespace":
return imp.namespaces.CastSet([]interface{}{val})
case "settings":
return imp.settings.CastSet(val)
case "allow", "deny":
return imp.permissions.CastResourcesSet(key, val)
@ -130,6 +136,7 @@ func (imp *Importer) Store(
rStore recordKeeper,
asStore automationScriptKeeper,
pk permissions.ImportKeeper,
sk settings.ImportKeeper,
roles sysTypes.RoleSet,
) (err error) {
err = imp.namespaces.Store(ctx, nsStore, mStore, cStore, pStore, rStore, asStore)
@ -148,5 +155,10 @@ func (imp *Importer) Store(
return errors.Wrap(err, "could not import permissions")
}
err = imp.settings.Store(ctx, sk)
if err != nil {
return errors.Wrap(err, "could not import settings")
}
return nil
}

View File

@ -4,6 +4,7 @@ import (
"context"
"github.com/cortezaproject/corteza-server/pkg/permissions"
"github.com/cortezaproject/corteza-server/pkg/settings"
)
type (
@ -19,6 +20,11 @@ type (
UpdateRoles(handle string, ID uint64)
Store(context.Context, permissions.ImportKeeper) error
}
SettingImporter interface {
CastSet(interface{}) error
Store(context.Context, settings.ImportKeeper) error
}
)
func NormalizeHandle(in string) string {

View File

@ -0,0 +1,64 @@
package settings
import (
"testing"
"github.com/jmoiron/sqlx/types"
)
func TestExport(t *testing.T) {
ss := ValueSet{}
ss = append(ss, ([]*Value{
&Value{
Name: "v_string",
Value: types.JSONText("\"string\""),
},
&Value{
Name: "v_float",
Value: types.JSONText("123"),
},
&Value{
Name: "v_float",
Value: types.JSONText("12.34"),
},
&Value{
Name: "v_bool",
Value: types.JSONText("true"),
},
&Value{
Name: "v_slice",
Value: types.JSONText("[1, \"string\", true]"),
},
&Value{
Name: "v_map",
Value: types.JSONText("{\"k1\": \"v1\",\"k2\": 2}"),
},
})...)
tt := Export(ss)
if _, ok := tt[0].Value.(string); !ok {
t.Errorf("Expecting %v to be string", tt[0].Value)
}
if _, ok := tt[1].Value.(float64); !ok {
t.Errorf("Expecting %v to be float64", tt[1].Value)
}
if _, ok := tt[2].Value.(float64); !ok {
t.Errorf("Expecting %v to be float64", tt[2].Value)
}
if _, ok := tt[3].Value.(bool); !ok {
t.Errorf("Expecting %v to be bool", tt[3].Value)
}
if _, ok := tt[4].Value.([]interface{}); !ok {
t.Errorf("Expecting %v to be []interface{}", tt[4].Value)
}
if _, ok := tt[5].Value.(map[string]interface{}); !ok {
t.Errorf("Expecting %v to be map[string]interface {}", tt[5].Value)
}
}

17
pkg/settings/exporter.go Normal file
View File

@ -0,0 +1,17 @@
package settings
import "gopkg.in/yaml.v2"
// Export transforms a given ValueSet into a yaml exportable structure
func Export(ss ValueSet) (o yaml.MapSlice) {
o = yaml.MapSlice{}
for _, s := range ss {
setting := yaml.MapItem{
Key: s.Name,
Value: s.Value.String(),
}
o = append(o, setting)
}
return o
}

55
pkg/settings/importer.go Normal file
View File

@ -0,0 +1,55 @@
package settings
import (
"context"
"github.com/pkg/errors"
"github.com/cortezaproject/corteza-server/pkg/deinterfacer"
"github.com/jmoiron/sqlx/types"
)
type (
Importer struct {
settings ValueSet
}
ImportKeeper interface {
BulkSet(vv ValueSet) (err error)
}
)
func NewImporter() *Importer {
return &Importer{}
}
// CastSet - resolves settings:
// <ValueSet> [ <Value>, ... ]
func (imp *Importer) CastSet(settings interface{}) (err error) {
if !deinterfacer.IsMap(settings) {
return errors.New("expecting map of settings")
}
return deinterfacer.Each(settings, func(_ int, name string, value interface{}) error {
return imp.addSetting(name, value)
})
}
func (imp *Importer) addSetting(name string, value interface{}) (err error) {
v, ok := value.(string)
if !ok {
return errors.New("value must be string")
}
setting := &Value{
Name: name,
Value: types.JSONText(v),
}
imp.settings = append(imp.settings, setting)
return nil
}
func (imp *Importer) Store(ctx context.Context, k ImportKeeper) (err error) {
return k.BulkSet(imp.settings)
}