Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

App List (gRPC series) #244

Merged
merged 2 commits into from
Aug 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Godeps/Godeps.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 35 additions & 23 deletions cmd/client/cmd/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,30 +157,42 @@ var appListCmd = &cobra.Command{
Short: "List all apps",
Long: "Return all apps with address and team.",
Example: " $ teresa app list",
RunE: func(cmd *cobra.Command, args []string) error {
tc := NewTeresa()
apps, err := tc.GetApps()
if err != nil {
return err
}
// rendering app info in a table view
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"TEAM", "APP", "ADDRESS"})
table.SetRowLine(true)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetRowSeparator("-")
table.SetAutoWrapText(false)
for _, app := range apps {
a := ""
if len(app.AddressList) > 0 {
a = app.AddressList[0]
}
r := []string{*app.Team, *app.Name, a}
table.Append(r)
Run: appList,
}

func appList(cmd *cobra.Command, args []string) {
conn, err := connection.New(cfgFile)
if err != nil {
client.PrintErrorAndExit("Error connecting to server: %v", err)
}
defer conn.Close()

cli := appb.NewAppClient(conn)
resp, err := cli.List(context.Background(), &appb.Empty{})
if err != nil {
client.PrintErrorAndExit(client.GetErrorMsg(err))
}

if len(resp.Apps) == 0 {
fmt.Println("You don't have any app")
return
}
// rendering app list in a table view
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"TEAM", "APP", "ADDRESS"})
table.SetRowLine(true)
table.SetAlignment(tablewriter.ALIGN_LEFT)
table.SetRowSeparator("-")
table.SetAutoWrapText(false)
for _, a := range resp.Apps {
urls := strings.Join(a.Urls, ",")
if urls == "" {
urls = "n/a"
}
table.Render()
return nil
},
r := []string{a.Team, a.Name, urls}
table.Append(r)
}
table.Render()
}

var appInfoCmd = &cobra.Command{
Expand Down
214 changes: 151 additions & 63 deletions pkg/protobuf/app/app.pb.go

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions pkg/protobuf/app/app.proto
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ service App {
rpc Info(InfoRequest) returns (InfoResponse);
rpc SetEnv(SetEnvRequest) returns (Empty);
rpc UnsetEnv(UnsetEnvRequest) returns (Empty);
rpc List(Empty) returns (ListResponse);
}

message CreateRequest {
Expand All @@ -34,6 +35,17 @@ message CreateRequest {
AutoScale auto_scale = 5;
}

message ListResponse {

message App {
string team = 1;
string name = 2;
repeated string urls = 3;
}
repeated App apps = 1;

}

message LogsRequest {
string name = 1;
int64 lines = 2;
Expand Down
28 changes: 28 additions & 0 deletions pkg/server/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type Operations interface {
HasPermission(user *storage.User, appName string) bool
SetEnv(user *storage.User, appName string, evs []*EnvVar) error
UnsetEnv(user *storage.User, appName string, evs []string) error
List(user *storage.User) ([]*AppListItem, error)
}

type K8sOperations interface {
Expand All @@ -47,6 +48,7 @@ type K8sOperations interface {
DeleteDeployEnvVars(namespace, name string, evNames []string) error
CreateOrUpdateDeployEnvVars(namespace, name string, evs []*EnvVar) error
DeleteNamespace(namespace string) error
NamespaceListByLabel(label, value string) ([]string, error)
}

type AppOperations struct {
Expand Down Expand Up @@ -335,6 +337,32 @@ func checkForProtectedEnvVars(evsNames []string) error {
return nil
}

func (ops *AppOperations) List(user *storage.User) ([]*AppListItem, error) {
teams, err := ops.tops.ListByUser(user.Email)
if err != nil {
return nil, err
}
items := make([]*AppListItem, 0)
for _, team := range teams {
apps, err := ops.kops.NamespaceListByLabel(TeresaTeamLabel, team.Name)
if err != nil {
return nil, err
}
for _, a := range apps {
addrs, err := ops.kops.AddressList(a)
if err != nil {
return nil, err
}
items = append(items, &AppListItem{
Team: team.Name,
Name: a,
Addresses: addrs,
})
}
}
return items, nil
}

func NewOperations(tops team.Operations, kops K8sOperations, st st.Storage) Operations {
return &AppOperations{tops: tops, kops: kops, st: st}
}
47 changes: 47 additions & 0 deletions pkg/server/app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ func (f *fakeK8sOperations) DeleteNamespace(namespace string) error {
return nil
}

func (f *fakeK8sOperations) NamespaceListByLabel(label, value string) ([]string, error) {
ns := make([]string, 0)
for s, _ := range f.Namespaces {
ns = append(ns, s)
}
return ns, nil
}

func (e *errK8sOperations) CreateNamespace(app *App, user string) error {
return e.NamespaceErr
}
Expand Down Expand Up @@ -202,6 +210,10 @@ func (e *errK8sOperations) DeleteNamespace(namespace string) error {
return e.DeleteNamespaceErr
}

func (e *errK8sOperations) NamespaceListByLabel(label, value string) ([]string, error) {
return nil, e.Err
}

func TestAppOperationsCreate(t *testing.T) {
tops := team.NewFakeOperations()
fakeSt := st.NewFake()
Expand Down Expand Up @@ -518,6 +530,41 @@ func TestAppOperationsInfoErrNotFound(t *testing.T) {
}
}

func TestAppOperationsList(t *testing.T) {
tops := team.NewFakeOperations()
appName := "teresa"
teamName := "luizalabs"

user := &storage.User{Email: "teresa@luizalabs.com"}
fk8s := &fakeK8sOperations{Namespaces: map[string]struct{}{appName: struct{}{}}}

ops := NewOperations(tops, fk8s, nil)
tops.(*team.FakeOperations).Storage[appName] = &storage.Team{
Name: teamName,
Users: []storage.User{*user},
}

apps, err := ops.List(user)
if err != nil {
t.Fatal("error getting app list:", err)
}

if len(apps) == 0 {
t.Fatal("expected at least one app")
}
for _, a := range apps {
if a.Name != appName {
t.Errorf("expected %s, got %s", appName, a.Name)
}
if a.Team != teamName {
t.Errorf("expected %s, got %s", teamName, a.Team)
}
if len(a.Addresses) != 1 { // see fakeK8sOperations.AddressList
t.Errorf("expected 1 address, got %d", len(a.Addresses))
}
}
}

func TestAppOperationsSetEnv(t *testing.T) {
tops := team.NewFakeOperations()
ops := NewOperations(tops, &fakeK8sOperations{}, nil)
Expand Down
15 changes: 15 additions & 0 deletions pkg/server/app/fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,21 @@ func (f *FakeOperations) Info(user *storage.User, appName string) (*Info, error)
return &Info{}, nil
}

func (f *FakeOperations) List(user *storage.User) ([]*AppListItem, error) {
f.mutex.RLock()
defer f.mutex.RUnlock()

items := make([]*AppListItem, 0)
for k, v := range f.Storage {
items = append(items, &AppListItem{
Team: v.Team,
Name: k,
Addresses: []*Address{&Address{Hostname: "localhost"}},
})
}
return items, nil
}

func (f *FakeOperations) TeamName(appName string) (string, error) {
return "luizalabs", nil
}
Expand Down
20 changes: 20 additions & 0 deletions pkg/server/app/fake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,26 @@ func TestFakeOperationsInfoErrNotFound(t *testing.T) {
}
}

func TestFakeOperationsList(t *testing.T) {
fake := NewFakeOperations()
user := &storage.User{Name: "gopher@luizalabs.com"}
app := &App{Name: "teresa"}
fake.(*FakeOperations).Storage[app.Name] = app

apps, err := fake.List(user)
if err != nil {
t.Fatal("error getting app info: ", err)
}

if len(apps) != 1 {
t.Fatal("expected a valid app, got nil")
}

if apps[0].Name != app.Name {
t.Errorf("expected test, got %s", apps[0].Name)
}
}

func TestFakeOperationsTeamName(t *testing.T) {
fake := NewFakeOperations()
teamName, err := fake.TeamName("teresa")
Expand Down
11 changes: 11 additions & 0 deletions pkg/server/app/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,17 @@ func (s *Service) UnsetEnv(ctx context.Context, req *appb.UnsetEnvRequest) (*app
return &appb.Empty{}, nil
}

func (s *Service) List(ctx context.Context, _ *appb.Empty) (*appb.ListResponse, error) {
user := ctx.Value("user").(*storage.User)

apps, err := s.ops.List(user)
if err != nil {
return nil, err
}

return newListResponse(apps), nil
}

func (s *Service) RegisterService(grpcServer *grpc.Server) {
appb.RegisterAppServer(grpcServer, s)
}
Expand Down
13 changes: 13 additions & 0 deletions pkg/server/app/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,19 @@ func TestInfoPermissionDenied(t *testing.T) {
}
}

func TestListSuccess(t *testing.T) {
fake := NewFakeOperations()
name := "teresa"
fake.(*FakeOperations).Storage[name] = &App{Name: name}
s := NewService(fake)
user := &storage.User{Email: "gopher@luizalabs.com"}
ctx := context.WithValue(context.Background(), "user", user)

if _, err := s.List(ctx, &appb.Empty{}); err != nil {
t.Error("Got error on list: ", err)
}
}

func TestSetEnvSuccess(t *testing.T) {
fake := NewFakeOperations()
name := "teresa"
Expand Down
31 changes: 28 additions & 3 deletions pkg/server/app/models.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package app

import (
appb "github.com/luizalabs/teresa-api/pkg/protobuf/app"
)
import appb "github.com/luizalabs/teresa-api/pkg/protobuf/app"

const (
ProcessTypeWeb = "web"
Expand Down Expand Up @@ -61,6 +59,12 @@ type Info struct {
Limits *Limits
}

type AppListItem struct {
Team string
Name string
Addresses []*Address
}

func newSliceLrq(s []*appb.CreateRequest_Limits_LimitRangeQuantity) []*LimitRangeQuantity {
var t []*LimitRangeQuantity
for _, tmp := range s {
Expand Down Expand Up @@ -237,3 +241,24 @@ func unsetEnvVars(app *App, evs []string) {
}
}
}

func newListResponse(items []*AppListItem) *appb.ListResponse {
if items == nil {
return nil
}

apps := make([]*appb.ListResponse_App, 0)
for _, item := range items {
addresses := make([]string, 0)
for _, addr := range item.Addresses {
addresses = append(addresses, addr.Hostname)
}
apps = append(apps, &appb.ListResponse_App{
Urls: addresses,
Name: item.Name,
Team: item.Team,
})
}

return &appb.ListResponse{Apps: apps}
}
26 changes: 26 additions & 0 deletions pkg/server/app/models_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,32 @@ func TestNewInfoResponse(t *testing.T) {
}
}

func TestNewListResponse(t *testing.T) {
items := []*AppListItem{{
Team: "luizalabs",
Addresses: []*Address{{Hostname: "host1"}},
Name: "teste",
}}

resp := newListResponse(items)
if len(items) != len(resp.Apps) {
t.Fatal("expected %d items, got %d", len(items), len(resp.Apps))
}
itemExpected := items[0]
itemActual := resp.Apps[0]
expectedUrl := itemExpected.Addresses[0].Hostname
actualUrl := itemActual.Urls[0]
if expectedUrl != actualUrl {
t.Errorf("expected %s, got %s", expectedUrl, actualUrl)
}
if itemExpected.Name != itemActual.Name {
t.Errorf("expected %s, got %s", itemExpected.Name, itemActual.Name)
}
if itemExpected.Team != itemActual.Team {
t.Errorf("expected %s, got %s", itemExpected.Team, itemActual.Team)
}
}

func TestSetEnvVars(t *testing.T) {
app := &App{Name: "teresa", Team: "luizalabs"}
var testCases = []struct {
Expand Down
Loading