Skip to content

Commit

Permalink
Refactor how tsh lists apps as text (#49773)
Browse files Browse the repository at this point in the history
This way it's going to be easier to add a column with target ports,
which should be displayed only if the list includes multi-port apps.
  • Loading branch information
ravicious authored Dec 5, 2024
1 parent 60647df commit 0bc6641
Show file tree
Hide file tree
Showing 2 changed files with 287 additions and 53 deletions.
150 changes: 150 additions & 0 deletions tool/tsh/common/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ import (
"crypto/tls"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os/user"
"strings"
"testing"
"time"

Expand All @@ -44,6 +46,7 @@ import (
"github.com/gravitational/teleport/lib/client"
"github.com/gravitational/teleport/lib/events"
"github.com/gravitational/teleport/lib/service/servicecfg"
"github.com/gravitational/teleport/lib/tlsca"
testserver "github.com/gravitational/teleport/tool/teleport/testenv"
)

Expand Down Expand Up @@ -597,3 +600,150 @@ uri: https://test-app.example.com:8443
})
}
}

func TestWriteAppTable(t *testing.T) {
defaultAppListings := []appListing{
appListing{
Proxy: "example.com",
Cluster: "foo-cluster",
App: mustMakeNewAppV3(t, types.Metadata{Name: "root-app"}, types.AppSpecV3{
// Short URLs, because in tests the width of the term is just 80 characters and the public
// address column gets truncated very early.
PublicAddr: "https://root-app.example.com",
URI: "http://localhost:8080",
}),
},
appListing{
Proxy: "example.com",
Cluster: "bar-cluster",
App: mustMakeNewAppV3(t, types.Metadata{Name: "leaf-app"}, types.AppSpecV3{
PublicAddr: "https://leaf-app.example.com",
URI: "http://localhost:4242",
}),
},
}

tests := []struct {
name string
config appTableConfig
appListings []appListing
wantHeaders []string
wantNoHeaders []string
wantValues []string
wantNoValues []string
}{
{
name: "regular list",
config: appTableConfig{
active: []tlsca.RouteToApp{},
verbose: false,
listAll: false,
},
appListings: defaultAppListings,
wantHeaders: []string{"Application", "Public Address"},
// Public addresses are expected to be truncated when verbose mode is off.
wantValues: []string{"https://root-app...", "https://leaf-app...", "root-app", "leaf-app"},
wantNoHeaders: []string{"URI", "Proxy", "Cluster"},
wantNoValues: []string{"http://localhost:8080", "foo-cluster", "bar-cluster"},
},
{
name: "regular list with active app",
config: appTableConfig{
active: []tlsca.RouteToApp{
tlsca.RouteToApp{Name: "root-app"},
},
verbose: false,
listAll: false,
},
appListings: defaultAppListings,
wantHeaders: []string{"Application"},
wantValues: []string{"> root-app", "leaf-app"},
},
{
name: "regular list with no apps",
config: appTableConfig{
active: []tlsca.RouteToApp{},
verbose: false,
listAll: false,
},
appListings: []appListing{},
wantHeaders: []string{"Application", "Public Address"},
},
{
name: "verbose",
config: appTableConfig{
active: []tlsca.RouteToApp{},
verbose: true,
listAll: false,
},
appListings: defaultAppListings,
wantHeaders: []string{"URI", "Application", "Public Address"},
wantValues: []string{"http://localhost:8080", "http://localhost:4242",
"https://root-app.example.com", "https://leaf-app.example.com", "root-app", "leaf-app"},
wantNoHeaders: []string{"Proxy", "Cluster"},
wantNoValues: []string{"foo-cluster", "bar-cluster"},
},
{
name: "list all",
config: appTableConfig{
active: []tlsca.RouteToApp{},
verbose: false,
listAll: true,
},
appListings: defaultAppListings,
wantHeaders: []string{"Proxy", "Cluster", "Application", "Public Address"},
// Public addresses are expected to be truncated when verbose mode is off.
wantValues: []string{"foo-cluste...", "bar-cluste...", "example.co...", "https://ro...", "https://le...", "root-app", "leaf-app"},
wantNoHeaders: []string{"URI"},
wantNoValues: []string{"http://localhost:8080"},
},
{
name: "verbose and list all",
config: appTableConfig{
active: []tlsca.RouteToApp{},
verbose: true,
listAll: true,
},
appListings: defaultAppListings,
wantHeaders: []string{"Proxy", "Cluster", "URI", "Application", "Public Address"},
wantValues: []string{"foo-cluster", "bar-cluster", "http://localhost:8080", "http://localhost:4242",
"https://root-app.example.com", "https://leaf-app.example.com", "root-app", "leaf-app"},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
var b bytes.Buffer
w := io.Writer(&b)

err := writeAppTable(w, test.appListings, test.config)
require.NoError(t, err)

lines := strings.SplitN(b.String(), "\n", 3)
headers := lines[0]
// The second line contains header separators ("------"), that's why it's skipped here.
values := lines[2]

for _, wantHeader := range test.wantHeaders {
assert.Contains(t, headers, wantHeader)
}
for _, wantNoHeader := range test.wantNoHeaders {
assert.NotContains(t, headers, wantNoHeader)
}

for _, wantValue := range test.wantValues {
assert.Contains(t, values, wantValue)
}
for _, wantNoValue := range test.wantNoValues {
assert.NotContains(t, values, wantNoValue)
}
})
}
}

func mustMakeNewAppV3(t *testing.T, meta types.Metadata, spec types.AppSpecV3) *types.AppV3 {
t.Helper()
app, err := types.NewAppV3(meta, spec)
require.NoError(t, err)
return app
}
Loading

0 comments on commit 0bc6641

Please sign in to comment.