Skip to content

Commit

Permalink
Namespaces rpc (#1421)
Browse files Browse the repository at this point in the history
  • Loading branch information
markphelps authored Mar 20, 2023
1 parent d98dee3 commit bdbe9be
Show file tree
Hide file tree
Showing 11 changed files with 1,330 additions and 725 deletions.
6 changes: 3 additions & 3 deletions internal/server/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ func (s *Server) ListFlags(ctx context.Context, r *flipt.ListFlagRequest) (*flip
return nil, err
}

var resp flipt.FlagList

resp.Flags = append(resp.Flags, results.Results...)
resp := flipt.FlagList{
Flags: results.Results,
}

total, err := s.store.CountFlags(ctx, r.NamespaceKey)
if err != nil {
Expand Down
88 changes: 88 additions & 0 deletions internal/server/namespace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package server

import (
"context"
"encoding/base64"

"go.flipt.io/flipt/errors"
"go.flipt.io/flipt/internal/storage"
flipt "go.flipt.io/flipt/rpc/flipt"
"go.uber.org/zap"
empty "google.golang.org/protobuf/types/known/emptypb"
)

// GetNamespace gets a namespace
func (s *Server) GetNamespace(ctx context.Context, r *flipt.GetNamespaceRequest) (*flipt.Namespace, error) {
s.logger.Debug("get namespace", zap.Stringer("request", r))
namespace, err := s.store.GetNamespace(ctx, r.Key)
s.logger.Debug("get namespace", zap.Stringer("response", namespace))
return namespace, err
}

// ListNamespaces lists all namespaces
func (s *Server) ListNamespaces(ctx context.Context, r *flipt.ListNamespaceRequest) (*flipt.NamespaceList, error) {
s.logger.Debug("list namespaces", zap.Stringer("request", r))

if r.Offset < 0 {
r.Offset = 0
}

opts := []storage.QueryOption{storage.WithLimit(uint64(r.Limit))}

if r.PageToken != "" {
tok, err := base64.StdEncoding.DecodeString(r.PageToken)
if err != nil {
return nil, errors.ErrInvalidf("pageToken is not valid: %q", r.PageToken)
}

opts = append(opts, storage.WithPageToken(string(tok)))
} else if r.Offset >= 0 {
// TODO: deprecate
opts = append(opts, storage.WithOffset(uint64(r.Offset)))
}

results, err := s.store.ListNamespaces(ctx, opts...)
if err != nil {
return nil, err
}

resp := flipt.NamespaceList{
Namespaces: results.Results,
}

total, err := s.store.CountNamespaces(ctx)
if err != nil {
return nil, err
}

resp.TotalCount = int32(total)
resp.NextPageToken = base64.StdEncoding.EncodeToString([]byte(results.NextPageToken))

s.logger.Debug("list namespaces", zap.Stringer("response", &resp))
return &resp, nil
}

// CreateNamespace creates a namespace
func (s *Server) CreateNamespace(ctx context.Context, r *flipt.CreateNamespaceRequest) (*flipt.Namespace, error) {
s.logger.Debug("create namespace", zap.Stringer("request", r))
namespace, err := s.store.CreateNamespace(ctx, r)
s.logger.Debug("create namespace", zap.Stringer("response", namespace))
return namespace, err
}

// UpdateNamespace updates an existing namespace
func (s *Server) UpdateNamespace(ctx context.Context, r *flipt.UpdateNamespaceRequest) (*flipt.Namespace, error) {
s.logger.Debug("update namespace", zap.Stringer("request", r))
namespace, err := s.store.UpdateNamespace(ctx, r)
s.logger.Debug("update namespace", zap.Stringer("response", namespace))
return namespace, err
}

// DeleteNamespace deletes a namespace
func (s *Server) DeleteNamespace(ctx context.Context, r *flipt.DeleteNamespaceRequest) (*empty.Empty, error) {
s.logger.Debug("delete namespace", zap.Stringer("request", r))
if err := s.store.DeleteNamespace(ctx, r); err != nil {
return nil, err
}
return &empty.Empty{}, nil
}
220 changes: 220 additions & 0 deletions internal/server/namespace_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
//nolint:goconst
package server

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"go.flipt.io/flipt/internal/storage"
flipt "go.flipt.io/flipt/rpc/flipt"
"go.uber.org/zap/zaptest"
)

func TestGetNamespace(t *testing.T) {
var (
store = &storeMock{}
logger = zaptest.NewLogger(t)
s = &Server{
logger: logger,
store: store,
}
req = &flipt.GetNamespaceRequest{Key: "foo"}
)

store.On("GetNamespace", mock.Anything, "foo").Return(&flipt.Namespace{
Key: req.Key,
}, nil)

got, err := s.GetNamespace(context.TODO(), req)
require.NoError(t, err)

assert.NotNil(t, got)
assert.Equal(t, "foo", got.Key)
}

func TestListNamespaces_PaginationOffset(t *testing.T) {
var (
store = &storeMock{}
logger = zaptest.NewLogger(t)
s = &Server{
logger: logger,
store: store,
}
)

defer store.AssertExpectations(t)

params := storage.QueryParams{}
store.On("ListNamespaces", mock.Anything, mock.MatchedBy(func(opts []storage.QueryOption) bool {
for _, opt := range opts {
opt(&params)
}

// assert offset is provided
return params.PageToken == "" && params.Offset > 0
})).Return(
storage.ResultSet[*flipt.Namespace]{
Results: []*flipt.Namespace{
{
Key: "foo",
},
},
NextPageToken: "bar",
}, nil)

store.On("CountNamespaces", mock.Anything).Return(uint64(1), nil)

got, err := s.ListNamespaces(context.TODO(), &flipt.ListNamespaceRequest{
Offset: 10,
})

require.NoError(t, err)

assert.NotEmpty(t, got.Namespaces)
assert.Equal(t, "YmFy", got.NextPageToken)
assert.Equal(t, int32(1), got.TotalCount)
}

func TestListNamespaces_PaginationPageToken(t *testing.T) {
var (
store = &storeMock{}
logger = zaptest.NewLogger(t)
s = &Server{
logger: logger,
store: store,
}
)

defer store.AssertExpectations(t)

params := storage.QueryParams{}
store.On("ListNamespaces", mock.Anything, mock.MatchedBy(func(opts []storage.QueryOption) bool {
for _, opt := range opts {
opt(&params)
}

// assert page token is preferred over offset
return params.PageToken == "foo" && params.Offset == 0
})).Return(
storage.ResultSet[*flipt.Namespace]{
Results: []*flipt.Namespace{
{
Key: "foo",
},
},
NextPageToken: "bar",
}, nil)

store.On("CountNamespaces", mock.Anything).Return(uint64(1), nil)

got, err := s.ListNamespaces(context.TODO(), &flipt.ListNamespaceRequest{
PageToken: "Zm9v",
Offset: 10,
})

require.NoError(t, err)

assert.NotEmpty(t, got.Namespaces)
assert.Equal(t, "YmFy", got.NextPageToken)
assert.Equal(t, int32(1), got.TotalCount)
}

func TestListNamespaces_PaginationInvalidPageToken(t *testing.T) {
var (
store = &storeMock{}
logger = zaptest.NewLogger(t)
s = &Server{
logger: logger,
store: store,
}
)

defer store.AssertExpectations(t)

store.AssertNotCalled(t, "ListNamespaces")

_, err := s.ListNamespaces(context.TODO(), &flipt.ListNamespaceRequest{
PageToken: "Invalid string",
Offset: 10,
})

assert.EqualError(t, err, `pageToken is not valid: "Invalid string"`)
}

func TestCreateNamespace(t *testing.T) {
var (
store = &storeMock{}
logger = zaptest.NewLogger(t)
s = &Server{
logger: logger,
store: store,
}
req = &flipt.CreateNamespaceRequest{
Key: "key",
Name: "name",
Description: "desc",
}
)

store.On("CreateNamespace", mock.Anything, req).Return(&flipt.Namespace{
Key: req.Key,
Name: req.Name,
Description: req.Description,
}, nil)

got, err := s.CreateNamespace(context.TODO(), req)
require.NoError(t, err)

assert.NotNil(t, got)
}

func TestUpdateNamespace(t *testing.T) {
var (
store = &storeMock{}
logger = zaptest.NewLogger(t)
s = &Server{
logger: logger,
store: store,
}
req = &flipt.UpdateNamespaceRequest{
Key: "key",
Name: "name",
Description: "desc",
}
)

store.On("UpdateNamespace", mock.Anything, req).Return(&flipt.Namespace{
Key: req.Key,
Name: req.Name,
Description: req.Description,
}, nil)

got, err := s.UpdateNamespace(context.TODO(), req)
require.NoError(t, err)

assert.NotNil(t, got)
}

func TestDeleteNamespace(t *testing.T) {
var (
store = &storeMock{}
logger = zaptest.NewLogger(t)
s = &Server{
logger: logger,
store: store,
}
req = &flipt.DeleteNamespaceRequest{
Key: "key",
}
)

store.On("DeleteNamespace", mock.Anything, req).Return(nil)

got, err := s.DeleteNamespace(context.TODO(), req)
require.NoError(t, err)

assert.NotNil(t, got)
}
6 changes: 3 additions & 3 deletions internal/server/rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ func (s *Server) ListRules(ctx context.Context, r *flipt.ListRuleRequest) (*flip
return nil, err
}

var resp flipt.RuleList

resp.Rules = append(resp.Rules, results.Results...)
resp := flipt.RuleList{
Rules: results.Results,
}

total, err := s.store.CountRules(ctx, r.NamespaceKey)
if err != nil {
Expand Down
6 changes: 3 additions & 3 deletions internal/server/segment.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ func (s *Server) ListSegments(ctx context.Context, r *flipt.ListSegmentRequest)
return nil, err
}

var resp flipt.SegmentList

resp.Segments = append(resp.Segments, results.Results...)
resp := flipt.SegmentList{
Segments: results.Results,
}

total, err := s.store.CountSegments(ctx, r.NamespaceKey)
if err != nil {
Expand Down
4 changes: 1 addition & 3 deletions internal/storage/sql/common/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,19 +169,17 @@ func (s *Store) CreateNamespace(ctx context.Context, r *flipt.CreateNamespaceReq
Key: r.Key,
Name: r.Name,
Description: r.Description,
Protected: r.Protected,
CreatedAt: now,
UpdatedAt: now,
}
)

if _, err := s.builder.Insert("namespaces").
Columns("\"key\"", "name", "description", "protected", "created_at", "updated_at").
Columns("\"key\"", "name", "description", "created_at", "updated_at").
Values(
namespace.Key,
namespace.Name,
namespace.Description,
namespace.Protected,
&fliptsql.Timestamp{Timestamp: namespace.CreatedAt},
&fliptsql.Timestamp{Timestamp: namespace.UpdatedAt},
).
Expand Down
Loading

0 comments on commit bdbe9be

Please sign in to comment.