mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: Match kubectl table style for simpler scripting (#1363)
Fixes #1322.
This commit is contained in:
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user