fix: Match kubectl table style for simpler scripting (#1363)

Fixes #1322.
This commit is contained in:
Kyle Carberry
2022-05-10 15:57:07 -05:00
committed by GitHub
parent 2df92e6fd3
commit e0a7aec228
6 changed files with 164 additions and 33 deletions
+43
View File
@@ -0,0 +1,43 @@
package cliui
import (
"strings"
"github.com/jedib0t/go-pretty/v6/table"
)
// Table creates a new table with standardized styles.
func Table() table.Writer {
tableWriter := table.NewWriter()
tableWriter.Style().Box.PaddingLeft = ""
tableWriter.Style().Box.PaddingRight = " "
tableWriter.Style().Options.DrawBorder = false
tableWriter.Style().Options.SeparateHeader = false
tableWriter.Style().Options.SeparateColumns = false
return tableWriter
}
// FilterTableColumns returns configurations to hide columns
// that are not provided in the array. If the array is empty,
// no filtering will occur!
func FilterTableColumns(header table.Row, columns []string) []table.ColumnConfig {
if len(columns) == 0 {
return nil
}
columnConfigs := make([]table.ColumnConfig, 0)
for _, headerTextRaw := range header {
headerText, _ := headerTextRaw.(string)
hidden := true
for _, column := range columns {
if strings.EqualFold(strings.ReplaceAll(column, "_", " "), headerText) {
hidden = false
break
}
}
columnConfigs = append(columnConfigs, table.ColumnConfig{
Name: headerText,
Hidden: hidden,
})
}
return columnConfigs
}
+61 -18
View File
@@ -2,7 +2,10 @@ package cli
import (
"fmt"
"strings"
"time"
"github.com/google/uuid"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
@@ -12,7 +15,10 @@ import (
)
func list() *cobra.Command {
return &cobra.Command{
var (
columns []string
)
cmd := &cobra.Command{
Annotations: workspaceCommand,
Use: "list",
Short: "List all workspaces",
@@ -22,11 +28,7 @@ func list() *cobra.Command {
if err != nil {
return err
}
organization, err := currentOrganization(cmd, client)
if err != nil {
return err
}
workspaces, err := client.WorkspacesByOwner(cmd.Context(), organization.ID, codersdk.Me)
workspaces, err := client.WorkspacesByUser(cmd.Context(), codersdk.Me)
if err != nil {
return err
}
@@ -37,11 +39,22 @@ func list() *cobra.Command {
_, _ = fmt.Fprintln(cmd.OutOrStdout())
return nil
}
users, err := client.Users(cmd.Context(), codersdk.UsersRequest{})
if err != nil {
return err
}
usersByID := map[uuid.UUID]codersdk.User{}
for _, user := range users {
usersByID[user.ID] = user
}
tableWriter := table.NewWriter()
tableWriter.SetStyle(table.StyleLight)
tableWriter.Style().Options.SeparateColumns = false
tableWriter.AppendHeader(table.Row{"Workspace", "Template", "Status", "Last Built", "Outdated"})
tableWriter := cliui.Table()
header := table.Row{"workspace", "template", "status", "last built", "outdated"}
tableWriter.AppendHeader(header)
tableWriter.SortBy([]table.SortBy{{
Name: "workspace",
}})
tableWriter.SetColumnConfigs(cliui.FilterTableColumns(header, columns))
for _, workspace := range workspaces {
status := ""
@@ -53,27 +66,54 @@ func list() *cobra.Command {
switch workspace.LatestBuild.Transition {
case database.WorkspaceTransitionStart:
status = "start"
status = "Running"
if inProgress {
status = "starting"
status = "Starting"
}
case database.WorkspaceTransitionStop:
status = "stop"
status = "Stopped"
if inProgress {
status = "stopping"
status = "Stopping"
}
case database.WorkspaceTransitionDelete:
status = "delete"
status = "Deleted"
if inProgress {
status = "deleting"
status = "Deleting"
}
}
if workspace.LatestBuild.Job.Status == codersdk.ProvisionerJobFailed {
status = "Failed"
}
duration := time.Now().UTC().Sub(workspace.LatestBuild.Job.CreatedAt).Truncate(time.Second)
if duration > time.Hour {
duration = duration.Truncate(time.Hour)
}
if duration > time.Minute {
duration = duration.Truncate(time.Minute)
}
days := 0
for duration.Hours() > 24 {
days++
duration -= 24 * time.Hour
}
durationDisplay := duration.String()
if days > 0 {
durationDisplay = fmt.Sprintf("%dd%s", days, durationDisplay)
}
if strings.HasSuffix(durationDisplay, "m0s") {
durationDisplay = durationDisplay[:len(durationDisplay)-2]
}
if strings.HasSuffix(durationDisplay, "h0m") {
durationDisplay = durationDisplay[:len(durationDisplay)-2]
}
user := usersByID[workspace.OwnerID]
tableWriter.AppendRow(table.Row{
cliui.Styles.Bold.Render(workspace.Name),
user.Username + "/" + workspace.Name,
workspace.TemplateName,
status,
workspace.LatestBuild.Job.CreatedAt.Format("January 2, 2006"),
durationDisplay,
workspace.Outdated,
})
}
@@ -81,4 +121,7 @@ func list() *cobra.Command {
return err
},
}
cmd.Flags().StringArrayVarP(&columns, "column", "c", nil,
"Specify a column to filter in the table.")
return cmd
}
+40
View File
@@ -0,0 +1,40 @@
package cli_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/cli/clitest"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/pty/ptytest"
)
func TestList(t *testing.T) {
t.Parallel()
t.Run("Single", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t, nil)
user := coderdtest.CreateFirstUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
cmd, root := clitest.New(t, "ls")
clitest.SetupConfig(t, client, root)
doneChan := make(chan struct{})
pty := ptytest.New(t)
cmd.SetIn(pty.Input())
cmd.SetOut(pty.Output())
go func() {
defer close(doneChan)
err := cmd.Execute()
require.NoError(t, err)
}()
pty.ExpectMatch(workspace.Name)
pty.ExpectMatch("Running")
<-doneChan
})
}
+2
View File
@@ -80,7 +80,9 @@ func Root() *cobra.Command {
cmd.PersistentFlags().String(varURL, "", "Specify the URL to your deployment.")
cmd.PersistentFlags().String(varToken, "", "Specify an authentication token.")
cliflag.String(cmd.PersistentFlags(), varAgentToken, "", "CODER_AGENT_TOKEN", "", "Specify an agent authentication token.")
_ = cmd.PersistentFlags().MarkHidden(varAgentToken)
cliflag.String(cmd.PersistentFlags(), varAgentURL, "", "CODER_AGENT_URL", "", "Specify the URL for an agent to access your deployment.")
_ = cmd.PersistentFlags().MarkHidden(varAgentURL)
cliflag.String(cmd.PersistentFlags(), varGlobalConfig, "", "CODER_CONFIG_DIR", configdir.LocalConfig("coderv2"), "Specify the path to the global `coder` config directory.")
cmd.PersistentFlags().Bool(varForceTty, false, "Force the `coder` command to run as if connected to a TTY.")
_ = cmd.PersistentFlags().MarkHidden(varForceTty)
+3 -6
View File
@@ -34,10 +34,8 @@ func templateList() *cobra.Command {
return nil
}
tableWriter := table.NewWriter()
tableWriter.SetStyle(table.StyleLight)
tableWriter.Style().Options.SeparateColumns = false
tableWriter.AppendHeader(table.Row{"Name", "Source", "Last Updated", "Used By"})
tableWriter := cliui.Table()
tableWriter.AppendHeader(table.Row{"Name", "Last Updated", "Used By"})
for _, template := range templates {
suffix := ""
@@ -45,8 +43,7 @@ func templateList() *cobra.Command {
suffix = "s"
}
tableWriter.AppendRow(table.Row{
cliui.Styles.Bold.Render(template.Name),
"Archive",
template.Name,
template.UpdatedAt.Format("January 2, 2006"),
cliui.Styles.Fuschia.Render(fmt.Sprintf("%d developer%s", template.WorkspaceOwnerCount, suffix)),
})
+15 -9
View File
@@ -2,17 +2,20 @@ package cli
import (
"fmt"
"sort"
"time"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
"github.com/coder/coder/cli/cliui"
"github.com/coder/coder/codersdk"
)
func userList() *cobra.Command {
return &cobra.Command{
var (
columns []string
)
cmd := &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
RunE: func(cmd *cobra.Command, args []string) error {
@@ -24,14 +27,14 @@ func userList() *cobra.Command {
if err != nil {
return err
}
sort.Slice(users, func(i, j int) bool {
return users[i].Username < users[j].Username
})
tableWriter := table.NewWriter()
tableWriter.SetStyle(table.StyleLight)
tableWriter.Style().Options.SeparateColumns = false
tableWriter.AppendHeader(table.Row{"Username", "Email", "Created At"})
tableWriter := cliui.Table()
header := table.Row{"Username", "Email", "Created At"}
tableWriter.AppendHeader(header)
tableWriter.SetColumnConfigs(cliui.FilterTableColumns(header, columns))
tableWriter.SortBy([]table.SortBy{{
Name: "Username",
}})
for _, user := range users {
tableWriter.AppendRow(table.Row{
user.Username,
@@ -43,4 +46,7 @@ func userList() *cobra.Command {
return err
},
}
cmd.Flags().StringArrayVarP(&columns, "column", "c", nil,
"Specify a column to filter in the table.")
return cmd
}