diff --git a/system/commands/importer.go b/system/commands/importer.go index 043a55a03..7e3cb5b75 100644 --- a/system/commands/importer.go +++ b/system/commands/importer.go @@ -6,12 +6,14 @@ import ( "os" "github.com/spf13/cobra" + "gopkg.in/yaml.v2" "github.com/cortezaproject/corteza-server/internal/auth" "github.com/cortezaproject/corteza-server/internal/permissions" "github.com/cortezaproject/corteza-server/pkg/cli" "github.com/cortezaproject/corteza-server/system/importer" "github.com/cortezaproject/corteza-server/system/service" + "github.com/cortezaproject/corteza-server/system/types" ) func Importer(ctx context.Context, c *cli.Config) *cobra.Command { @@ -24,6 +26,7 @@ func Importer(ctx context.Context, c *cli.Config) *cobra.Command { c.InitServices(ctx, c) var ( + aux interface{} ff []io.Reader err error ) @@ -41,18 +44,25 @@ func Importer(ctx context.Context, c *cli.Config) *cobra.Command { ff = []io.Reader{os.Stdin} } - roles := service.DefaultRole.With(ctx) + roles, err := service.DefaultRole.With(ctx).Find(&types.RoleFilter{}) + cli.HandleError(err) for i, f := range ff { cmd.Printf("Importing from %s\n", args[i]) - imp := importer.NewImporter( - roles, - permissions.NewImporter(service.DefaultAccessControl.Whitelist()), + cli.HandleError(yaml.NewDecoder(f).Decode(&aux)) + + perm := permissions.NewImporter(service.DefaultAccessControl.Whitelist()) + + imp := importer.NewImporter(perm, + importer.NewRoleImport(perm, roles), ) - cli.HandleError(imp.YAML(f)) - cli.HandleError(imp.Store(ctx, roles, service.DefaultAccessControl)) + cli.HandleError(imp.Store( + ctx, + service.DefaultRole.With(ctx), + service.DefaultAccessControl, + )) } }, } diff --git a/system/importer/importer.go b/system/importer/importer.go index fdec63a8f..52bab3d4b 100644 --- a/system/importer/importer.go +++ b/system/importer/importer.go @@ -3,9 +3,6 @@ package importer import ( "context" "fmt" - "io" - - "gopkg.in/yaml.v2" "github.com/cortezaproject/corteza-server/internal/permissions" "github.com/cortezaproject/corteza-server/pkg/deinterfacer" @@ -14,32 +11,18 @@ import ( type ( Importer struct { - roleFinder roleFinder - - roles *RoleImport - + roles *Role permissions importer.PermissionImporter } ) -func NewImporter(rf roleFinder, p importer.PermissionImporter) *Importer { +func NewImporter(p importer.PermissionImporter, ri *Role) *Importer { return &Importer{ - roleFinder: rf, - roles: NewRoleImporter(rf, p), + roles: ri, permissions: p, } } -func (imp *Importer) YAML(r io.Reader) (err error) { - var aux interface{} - - if err = yaml.NewDecoder(r).Decode(&aux); err != nil { - return - } - - return imp.Cast(aux) -} - func (imp *Importer) Cast(in interface{}) (err error) { return deinterfacer.Each(in, func(index int, key string, val interface{}) (err error) { switch key { diff --git a/system/importer/main_test.go b/system/importer/main_test.go new file mode 100644 index 000000000..9052da558 --- /dev/null +++ b/system/importer/main_test.go @@ -0,0 +1,65 @@ +package importer + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v2" + + "github.com/cortezaproject/corteza-server/internal/permissions" + "github.com/cortezaproject/corteza-server/system/service" +) + +var ( + pi *permissions.Importer + + imp *Importer +) + +func TestMain(m *testing.M) { + resetMocks() + os.Exit(m.Run()) +} + +func resetMocks() { + // whitelist = nil, anything can be added + pi = permissions.NewImporter(service.AccessControl(nil).Whitelist()) + + imp = NewImporter( + pi, + NewRoleImport(pi, nil), + ) +} + +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)) + req.NoError(err) + req.NoError(yaml.NewDecoder(f).Decode(&aux)) + req.NotNil(aux) + + if reqError, ok := tester.(error); ok { + req.EqualError(imp.Cast(aux), reqError.Error()) + return + } else { + req.NoError(imp.Cast(aux)) + } + + switch tester := tester.(type) { + case func(*testing.T, *Role): + tester(t, imp.roles) + case func(*testing.T, *Importer): + tester(t, imp) + default: + panic("unsupported tester function signature") + } + }) +} diff --git a/system/importer/role.go b/system/importer/role.go index 058e219c8..ebc7fcf08 100644 --- a/system/importer/role.go +++ b/system/importer/role.go @@ -12,17 +12,10 @@ import ( ) type ( - RoleImport struct { - set types.RoleSet - dirty map[string]bool - + Role struct { + set types.RoleSet + dirty map[uint64]bool permissions importer.PermissionImporter - - finder roleFinder - } - - roleFinder interface { - FindByHandle(string) (*types.Role, error) } roleKeeper interface { @@ -31,31 +24,32 @@ type ( } ) -func NewRoleImporter(finder roleFinder, permissions importer.PermissionImporter) *RoleImport { - return &RoleImport{ - set: types.RoleSet{}, - dirty: map[string]bool{}, - permissions: permissions, - finder: finder, +func NewRoleImport(permissions importer.PermissionImporter, set types.RoleSet) *Role { + if set == nil { + set = types.RoleSet{} } + + out := &Role{ + set: set, + dirty: make(map[uint64]bool), + permissions: permissions, + } + + return out } -// Resolves permission rules: -// { : { role } } or [ { role }, ... ] -func (imp *RoleImport) CastSet(set interface{}) error { +func (rImp *Role) CastSet(set interface{}) error { return deinterfacer.Each(set, func(index int, handle string, def interface{}) error { if index > -1 { // Roles defined as collection deinterfacer.KVsetString(&handle, "handle", def) } - return imp.Cast(handle, def) + return rImp.Cast(handle, def) }) } -// Resolves permission rules: -// { : { role } } or [ { role }, ... ] -func (imp *RoleImport) Cast(handle string, def interface{}) (err error) { +func (rImp *Role) Cast(handle string, def interface{}) (err error) { var role *types.Role if !importer.IsValidHandle(handle) { @@ -63,12 +57,21 @@ func (imp *RoleImport) Cast(handle string, def interface{}) (err error) { } handle = importer.NormalizeHandle(handle) - if role, err = imp.Get(handle); err != nil { + if role, err = rImp.Get(handle); err != nil { return err + } else if role == nil { + role = &types.Role{ + Handle: handle, + } + + rImp.set = append(rImp.set, role) + } else if role.ID == 0 { + return errors.Errorf("role handle %q already defined in this import session", role.Handle) + } else { + rImp.dirty[role.ID] = true } if name, ok := def.(string); ok && name != "" { - imp.dirty[handle] = role.Name != name role.Name = name return nil } @@ -79,11 +82,10 @@ func (imp *RoleImport) Cast(handle string, def interface{}) (err error) { // already handled case "name": name := deinterfacer.ToString(val) - imp.dirty[handle] = role.Name != name role.Name = name case "allow", "deny": - return imp.permissions.CastSet(types.RolePermissionResource.String()+role.Handle, key, val) + return rImp.permissions.CastSet(types.RolePermissionResource.String()+role.Handle, key, val) default: return fmt.Errorf("unexpected key %q for role %q", key, role.Handle) @@ -93,62 +95,32 @@ func (imp *RoleImport) Cast(handle string, def interface{}) (err error) { }) } -// Exists returns true if role exists in the buffer or -// can be loaded from the storage -func (imp *RoleImport) Exists(handle string) bool { - handle = importer.NormalizeHandle(handle) - role := imp.set.FindByHandle(handle) - if role != nil { - return true - } - - if imp.finder != nil { - role, err := imp.finder.FindByHandle(handle) - if err == nil && role != nil { - imp.set = append(imp.set, role) - return true - } - } - - return false -} - -// finds or makes new role -func (imp *RoleImport) Get(handle string) (*types.Role, error) { +func (rImp *Role) Get(handle string) (*types.Role, error) { handle = importer.NormalizeHandle(handle) if !importer.IsValidHandle(handle) { return nil, errors.New("invalid role handle") } - if !imp.Exists(handle) { - imp.set = append(imp.set, &types.Role{ - Handle: handle, - Name: handle, - }) - } - - return imp.set.FindByHandle(handle), nil + return rImp.set.FindByHandle(handle), nil } -func (imp *RoleImport) Store(ctx context.Context, k roleKeeper) error { - return imp.set.Walk(func(role *types.Role) (err error) { +func (rImp *Role) Store(ctx context.Context, k roleKeeper) error { + return rImp.set.Walk(func(role *types.Role) (err error) { var handle = role.Handle if role.ID == 0 { role, err = k.Create(role) - } else { - if imp.dirty[handle] { - role, err = k.Update(role) - } + } else if rImp.dirty[role.ID] { + role, err = k.Update(role) } if err != nil { return } - imp.permissions.UpdateResources(types.RolePermissionResource.String(), handle, role.ID) - imp.permissions.UpdateRoles(role.Handle, role.ID) + rImp.permissions.UpdateResources(types.RolePermissionResource.String(), handle, role.ID) + rImp.permissions.UpdateRoles(role.Handle, role.ID) return }) diff --git a/system/importer/role_test.go b/system/importer/role_test.go index 0e9317c04..bcf710a9d 100644 --- a/system/importer/role_test.go +++ b/system/importer/role_test.go @@ -2,42 +2,20 @@ package importer import ( "testing" + + "github.com/stretchr/testify/require" ) func TestRoleImport_CastSet(t *testing.T) { - t.Skip() - // tests := []struct { - // name string - // resource string - // yaml string - // set types.RoleSet - // }{ - // {name: "empty", yaml: ``}, - // {name: "empty map", yaml: `{}`}, - // {name: "empty slice", yaml: `[]`}, - // { - // name: "full", - // yaml: ` - // admins: Admins - // foo: - // name: Foo - // bar: - // `, - // set: []*types.Role{ - // {Handle: "admins", Name: "Admins"}, - // {Handle: "foo", Name: "Foo"}, - // {Handle: "bar", Name: "bar"}, - // }, - // }, - // } - // for _, tt := range tests { - // t.Run(tt.name, func(t *testing.T) { - // imp := &RoleImport{} - // - // aux, err := importer.ParseYAML([]byte(tt.yaml)) - // require.NoError(t, err) - // require.NoError(t, imp.CastSet(aux)) - // require.Equal(t, tt.set, imp.set) - // }) - // } + impFixTester(t, "roles", func(t *testing.T, ri *Role) { + req := require.New(t) + req.NotNil(ri.set) + req.Len(ri.set, 2) + + req.NotNil(ri.set.FindByHandle("r1")) + req.Equal("Role1", ri.set.FindByHandle("r1").Name) + + req.NotNil(ri.set.FindByHandle("r2")) + req.Equal("Role2", ri.set.FindByHandle("r2").Name) + }) } diff --git a/system/importer/testdata/roles.yaml b/system/importer/testdata/roles.yaml new file mode 100644 index 000000000..228ba2ffa --- /dev/null +++ b/system/importer/testdata/roles.yaml @@ -0,0 +1,4 @@ +roles: + r1: Role1 + r2: + name: Role2