diff --git a/compose/commands/exporter.go b/compose/commands/exporter.go index 08bf89830..55ec0597a 100644 --- a/compose/commands/exporter.go +++ b/compose/commands/exporter.go @@ -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{} diff --git a/compose/importer/default.go b/compose/importer/default.go index 73abad207..cf07ca325 100644 --- a/compose/importer/default.go +++ b/compose/importer/default.go @@ -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, ) } diff --git a/compose/importer/importer.go b/compose/importer/importer.go index 6f28e7f4f..b739d4b2f 100644 --- a/compose/importer/importer.go +++ b/compose/importer/importer.go @@ -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 } diff --git a/pkg/importer/importer.go b/pkg/importer/importer.go index 9700b4ba5..a4647b3e6 100644 --- a/pkg/importer/importer.go +++ b/pkg/importer/importer.go @@ -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 { diff --git a/pkg/settings/export_test.go b/pkg/settings/export_test.go new file mode 100644 index 000000000..2a9c1da29 --- /dev/null +++ b/pkg/settings/export_test.go @@ -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) + } +} diff --git a/pkg/settings/exporter.go b/pkg/settings/exporter.go new file mode 100644 index 000000000..e32e31487 --- /dev/null +++ b/pkg/settings/exporter.go @@ -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 +} diff --git a/pkg/settings/importer.go b/pkg/settings/importer.go new file mode 100644 index 000000000..10b6146a0 --- /dev/null +++ b/pkg/settings/importer.go @@ -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: +// [ , ... ] +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) +}