From 4be172d988cc20e3892776bd8857c6a98ee1fa64 Mon Sep 17 00:00:00 2001 From: Michael Desa Date: Wed, 11 Oct 2017 17:50:34 -0400 Subject: [PATCH] WIP: Add BoltDB implementation of RolesStore Signed-off-by: Jared Scheib --- bolt/client.go | 6 ++ bolt/internal/internal.go | 59 ++++++++++++++++ bolt/roles.go | 138 ++++++++++++++++++++++++++++++++++++++ bolt/roles_test.go | 51 ++++++++++++++ chronograf.go | 1 + 5 files changed, 255 insertions(+) create mode 100644 bolt/roles.go create mode 100644 bolt/roles_test.go diff --git a/bolt/client.go b/bolt/client.go index 577541ab92..091e43429f 100644 --- a/bolt/client.go +++ b/bolt/client.go @@ -20,6 +20,7 @@ type Client struct { ServersStore *ServersStore LayoutStore *LayoutStore UsersStore *UsersStore + RolesStore *RolesStore DashboardsStore *DashboardsStore } @@ -29,6 +30,7 @@ func NewClient() *Client { c.SourcesStore = &SourcesStore{client: c} c.ServersStore = &ServersStore{client: c} c.UsersStore = &UsersStore{client: c} + c.RolesStore = &RolesStore{client: c} c.LayoutStore = &LayoutStore{ client: c, IDs: &uuid.V4{}, @@ -70,6 +72,10 @@ func (c *Client) Open(ctx context.Context) error { if _, err := tx.CreateBucketIfNotExists(UsersBucket); err != nil { return err } + // Always create Roles bucket. + if _, err := tx.CreateBucketIfNotExists(RolesBucket); err != nil { + return err + } return nil }); err != nil { return err diff --git a/bolt/internal/internal.go b/bolt/internal/internal.go index 8d79dde745..f4bb90f52b 100644 --- a/bolt/internal/internal.go +++ b/bolt/internal/internal.go @@ -439,3 +439,62 @@ func UnmarshalUserPB(data []byte, u *User) error { } return nil } + +// MarshalRole encodes a role to binary protobuf format. +func MarshalRole(r *chronograf.Role) ([]byte, error) { + pbUsers := make([]uint64, len(r.Users)) + for i, u := range r.Users { + pbUsers[i] = u.ID + } + + pbPermissions := make([]string, len(r.Permissions)) + for i, m := range r.Permissions { + pbPermissions[i] = m.Name + } + + return MarshalRolePB(&Role{ + Name: r.Name, + Permissions: pbPermissions, + Users: pbUsers, + }) +} + +// MarshalRolePB encodes a role to binary protobuf format. +func MarshalRolePB(r *Role) ([]byte, error) { + return proto.Marshal(r) +} + +// UnmarshalRole decodes a role from binary protobuf data. +func UnmarshalRole(data []byte, r *chronograf.Role) error { + var pb Role + if err := UnmarshalRolePB(data, &pb); err != nil { + return err + } + + perms := make(chronograf.Permissions, len(pb.Permissions)) + for i, m := range pb.Permissions { + perms[i] = chronograf.Permission{ + Name: m, + } + } + + users := make([]chronograf.User, len(pb.Users)) + for i, u := range pb.Users { + users[i] = chronograf.User{ + ID: u, + } + } + + r.Name = pb.Name + r.Permissions = perms + r.Users = users + return nil +} + +// UnmarshalRolePB decodes a role from binary protobuf data. +func UnmarshalRolePB(data []byte, r *Role) error { + if err := proto.Unmarshal(data, r); err != nil { + return err + } + return nil +} diff --git a/bolt/roles.go b/bolt/roles.go new file mode 100644 index 0000000000..fce178f96b --- /dev/null +++ b/bolt/roles.go @@ -0,0 +1,138 @@ +package bolt + +import ( + "context" + + "github.com/boltdb/bolt" + "github.com/influxdata/chronograf" + "github.com/influxdata/chronograf/bolt/internal" +) + +// Ensure RolesStore implements chronograf.RolesStore. +var _ chronograf.RolesStore = &RolesStore{} + +// RolesBucket is used to store roles local to chronograf +var RolesBucket = []byte("RolesV1") + +// RolesStore uses bolt to store and retrieve roles +type RolesStore struct { + client *Client +} + +// get searches the RolesStore for role with name and returns the chronograf representation +func (s *RolesStore) get(ctx context.Context, name string) (*chronograf.Role, error) { + found := false + var role *chronograf.Role + err := s.client.db.View(func(tx *bolt.Tx) error { + err := tx.Bucket(RolesBucket).ForEach(func(k, v []byte) error { + var r chronograf.Role + if err := internal.UnmarshalRole(v, &r); err != nil { + return err + } else if role.Name != name { + return nil + } + found = true + role = &r + return nil + }) + if err != nil { + return err + } + if found == false { + return chronograf.ErrRoleNotFound + } + return nil + }) + if err != nil { + return nil, err + } + + return role, nil +} + +// Get searches the RolesStore for role with name +func (s *RolesStore) Get(ctx context.Context, name string) (*chronograf.Role, error) { + r, err := s.get(ctx, name) + if err != nil { + return nil, err + } + return r, nil +} + +// Add a new Roles in the RolesStore. +func (s *RolesStore) Add(ctx context.Context, r *chronograf.Role) (*chronograf.Role, error) { + if err := s.client.db.Update(func(tx *bolt.Tx) error { + b := tx.Bucket(RolesBucket) + if v, err := internal.MarshalRole(r); err != nil { + return err + } else if err := b.Put([]byte(r.Name), v); err != nil { + return err + } + return nil + }); err != nil { + return nil, err + } + + return r, nil +} + +// Delete the roles from the RolesStore +func (s *RolesStore) Delete(ctx context.Context, role *chronograf.Role) error { + r, err := s.get(ctx, role.Name) + if err != nil { + return err + } + if err := s.client.db.Update(func(tx *bolt.Tx) error { + if err := tx.Bucket(RolesBucket).Delete([]byte(r.Name)); err != nil { + return err + } + return nil + }); err != nil { + return err + } + + return nil +} + +// Update a role +func (s *RolesStore) Update(ctx context.Context, role *chronograf.Role) error { + r, err := s.get(ctx, role.Name) + if err != nil { + return err + } + if err := s.client.db.Update(func(tx *bolt.Tx) error { + r.Name = role.Name + if v, err := internal.MarshalRole(r); err != nil { + return err + } else if err := tx.Bucket(RolesBucket).Put([]byte(r.Name), v); err != nil { + return err + } + return nil + }); err != nil { + return err + } + + return nil +} + +// All returns all roles +func (s *RolesStore) All(ctx context.Context) ([]chronograf.Role, error) { + var roles []chronograf.Role + if err := s.client.db.View(func(tx *bolt.Tx) error { + if err := tx.Bucket(RolesBucket).ForEach(func(k, v []byte) error { + var role chronograf.Role + if err := internal.UnmarshalRole(v, &role); err != nil { + return err + } + roles = append(roles, role) + return nil + }); err != nil { + return err + } + return nil + }); err != nil { + return nil, err + } + + return roles, nil +} diff --git a/bolt/roles_test.go b/bolt/roles_test.go new file mode 100644 index 0000000000..fe9bb17651 --- /dev/null +++ b/bolt/roles_test.go @@ -0,0 +1,51 @@ +package bolt_test + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/influxdata/chronograf" +) + +func TestRolesStore_Get(t *testing.T) { + type args struct { + ctx context.Context + name string + } + tests := []struct { + name string + args args + want *chronograf.Role + wantErr bool + }{ + { + name: "Role not found", + args: args{ + ctx: context.Background(), + name: "unknown", + }, + wantErr: true, + }, + } + for _, tt := range tests { + client, err := NewTestClient() + if err != nil { + t.Fatal(err) + } + if err := client.Open(context.TODO()); err != nil { + t.Fatal(err) + } + defer client.Close() + + s := client.RolesStore + got, err := s.Get(tt.args.ctx, tt.args.name) + if (err != nil) != tt.wantErr { + t.Errorf("%q. RolesStore.Get() error = %v, wantErr %v", tt.name, err, tt.wantErr) + continue + } + if !cmp.Equal(got, tt.want) { + t.Errorf("%q. RolesStore.Get() = %v, want %v", tt.name, got, tt.want) + } + } +} diff --git a/chronograf.go b/chronograf.go index 7530685838..d912929a59 100644 --- a/chronograf.go +++ b/chronograf.go @@ -26,6 +26,7 @@ const ( ErrLayoutNotFound = Error("layout not found") ErrDashboardNotFound = Error("dashboard not found") ErrUserNotFound = Error("user not found") + ErrRoleNotFound = Error("role not found") ErrLayoutInvalid = Error("layout is invalid") ErrAlertNotFound = Error("alert not found") ErrAuthentication = Error("user not authenticated")