Add compose setting import/export
This commit is contained in:
parent
b06266c68b
commit
cb202d474d
@ -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{}
|
||||
|
||||
|
||||
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
64
pkg/settings/export_test.go
Normal file
64
pkg/settings/export_test.go
Normal 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
17
pkg/settings/exporter.go
Normal 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
55
pkg/settings/importer.go
Normal 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)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user