Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit 9b1173e

Browse files
committed
Add table output to envs ls
1 parent b957f37 commit 9b1173e

File tree

8 files changed

+125
-25
lines changed

8 files changed

+125
-25
lines changed

ci/integration/integration_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func TestCoderCLI(t *testing.T) {
4949
headlessLogin(ctx, t, c)
5050

5151
c.Run(ctx, "coder envs").Assert(t,
52-
tcli.Success(),
52+
tcli.Error(),
5353
)
5454

5555
c.Run(ctx, "coder envs ls").Assert(t,

cmd/coder/envs.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
package main
22

33
import (
4-
"fmt"
4+
"encoding/json"
5+
"os"
56

7+
"cdr.dev/coder-cli/internal/x/xtabwriter"
68
"github.com/urfave/cli"
9+
10+
"go.coder.com/flog"
711
)
812

913
func makeEnvsCommand() cli.Command {
14+
var outputFmt string
1015
return cli.Command{
1116
Name: "envs",
1217
Usage: "Interact with Coder environments",
1318
Description: "Perform operations on the Coder environments owned by the active user.",
19+
Action: exitHelp,
1420
Subcommands: []cli.Command{
1521
{
1622
Name: "ls",
@@ -21,11 +27,27 @@ func makeEnvsCommand() cli.Command {
2127
entClient := requireAuth()
2228
envs := getEnvs(entClient)
2329

24-
for _, env := range envs {
25-
fmt.Println(env.Name)
30+
switch outputFmt {
31+
case "human":
32+
err := xtabwriter.WriteTable(len(envs), func(i int) interface{} {
33+
return envs[i]
34+
})
35+
requireSuccess(err, "failed to write table: %v", err)
36+
case "json":
37+
err := json.NewEncoder(os.Stdout).Encode(envs)
38+
requireSuccess(err, "failed to write json: %v", err)
39+
default:
40+
flog.Fatal("unknown --output value %q", outputFmt)
2641
}
2742
},
28-
Flags: nil,
43+
Flags: []cli.Flag{
44+
cli.StringFlag{
45+
Name: "output",
46+
Usage: "json | human",
47+
Value: "human",
48+
Destination: &outputFmt,
49+
},
50+
},
2951
},
3052
},
3153
}

cmd/coder/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ func main() {
4040
flog.Fatal("command %q not found", s)
4141
}
4242
app.Email = "support@coder.com"
43+
app.Action = exitHelp
4344

4445
app.Commands = []cli.Command{
4546
makeLoginCmd(),
@@ -64,3 +65,7 @@ func requireSuccess(err error, msg string, args ...interface{}) {
6465
flog.Fatal(msg, args...)
6566
}
6667
}
68+
69+
func exitHelp(c *cli.Context) {
70+
cli.ShowCommandHelpAndExit(c, c.Command.FullName(), 1)
71+
}

cmd/coder/secrets.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ func makeSecretsCmd() cli.Command {
1919
Name: "secrets",
2020
Usage: "Interact with Coder Secrets",
2121
Description: "Interact with secrets objects owned by the active user.",
22+
Action: exitHelp,
2223
Subcommands: []cli.Command{
2324
{
2425
Name: "ls",
@@ -30,7 +31,7 @@ func makeSecretsCmd() cli.Command {
3031
Name: "rm",
3132
Usage: "Remove one or more secrets by name",
3233
ArgsUsage: "[...secret_name]",
33-
Action: removeSecret,
34+
Action: removeSecrets,
3435
},
3536
{
3637
Name: "view",
@@ -171,7 +172,7 @@ func viewSecret(c *cli.Context) {
171172
requireSuccess(err, "failed to write: %v", err)
172173
}
173174

174-
func removeSecret(c *cli.Context) {
175+
func removeSecrets(c *cli.Context) {
175176
var (
176177
client = requireAuth()
177178
names = append([]string{c.Args().First()}, c.Args().Tail()...)

cmd/coder/users.go

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package main
22

33
import (
44
"encoding/json"
5-
"fmt"
65
"os"
76

87
"cdr.dev/coder-cli/internal/x/xtabwriter"
@@ -14,8 +13,9 @@ import (
1413
func makeUsersCmd() cli.Command {
1514
var output string
1615
return cli.Command{
17-
Name: "users",
18-
Usage: "Interact with Coder user accounts",
16+
Name: "users",
17+
Usage: "Interact with Coder user accounts",
18+
Action: exitHelp,
1919
Subcommands: []cli.Command{
2020
{
2121
Name: "ls",
@@ -24,7 +24,7 @@ func makeUsersCmd() cli.Command {
2424
Flags: []cli.Flag{
2525
cli.StringFlag{
2626
Name: "output",
27-
Usage: "(json | human)",
27+
Usage: "json | human",
2828
Value: "human",
2929
Destination: &output,
3030
},
@@ -43,17 +43,10 @@ func listUsers(outputFmt *string) func(c *cli.Context) {
4343

4444
switch *outputFmt {
4545
case "human":
46-
w := xtabwriter.NewWriter()
47-
if len(users) > 0 {
48-
_, err = fmt.Fprintln(w, xtabwriter.StructFieldNames(users[0]))
49-
requireSuccess(err, "failed to write: %v", err)
50-
}
51-
for _, u := range users {
52-
_, err = fmt.Fprintln(w, xtabwriter.StructValues(u))
53-
requireSuccess(err, "failed to write: %v", err)
54-
}
55-
err = w.Flush()
56-
requireSuccess(err, "failed to flush writer: %v", err)
46+
err := xtabwriter.WriteTable(len(users), func(i int) interface{} {
47+
return users[i]
48+
})
49+
requireSuccess(err, "failed to write table: %v", err)
5750
case "json":
5851
err = json.NewEncoder(os.Stdout).Encode(users)
5952
requireSuccess(err, "failed to encode users to json: %v", err)

internal/entclient/env.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,33 @@ import (
44
"context"
55
"time"
66

7+
"cdr.dev/coder-cli/internal/x/xjson"
78
"nhooyr.io/websocket"
89
)
910

1011
// Environment describes a Coder environment
1112
type Environment struct {
12-
Name string `json:"name"`
13-
ID string `json:"id"`
13+
ID string `json:"id" tab:"-"`
14+
Name string `json:"name"`
15+
ImageID string `json:"image_id" tab:"-"`
16+
ImageTag string `json:"image_tag"`
17+
OrganizationID string `json:"organization_id" tab:"-"`
18+
UserID string `json:"user_id" tab:"-"`
19+
LastBuiltAt time.Time `json:"last_built_at" tab:"-"`
20+
CPUCores float32 `json:"cpu_cores"`
21+
MemoryGB int `json:"memory_gb"`
22+
DiskGB int `json:"disk_gb"`
23+
GPUs int `json:"gpus"`
24+
Updating bool `json:"updating"`
25+
RebuildMessages []struct {
26+
Text string `json:"text"`
27+
Required bool `json:"required"`
28+
} `json:"rebuild_messages" tab:"-"`
29+
CreatedAt time.Time `json:"created_at" tab:"-"`
30+
UpdatedAt time.Time `json:"updated_at" tab:"-"`
31+
LastOpenedAt time.Time `json:"last_opened_at" tab:"-"`
32+
LastConnectionAt time.Time `json:"last_connection_at" tab:"-"`
33+
AutoOffThreshold xjson.Duration `json:"auto_off_threshold" tab:"-"`
1434
}
1535

1636
// Envs gets the list of environments owned by the authenticated user

internal/x/xjson/duration.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package xjson
2+
3+
import (
4+
"encoding/json"
5+
"strconv"
6+
"time"
7+
)
8+
9+
// Duration is a time.Duration that marshals to millisecond precision.
10+
// Most javascript applications expect durations to be in milliseconds.
11+
type Duration time.Duration
12+
13+
// MarshalJSON marshals the duration to millisecond precision.
14+
func (d Duration) MarshalJSON() ([]byte, error) {
15+
du := time.Duration(d)
16+
return json.Marshal(du.Milliseconds())
17+
}
18+
19+
// UnmarshalJSON unmarshals a millisecond-precision integer to
20+
// a time.Duration.
21+
func (d *Duration) UnmarshalJSON(b []byte) error {
22+
i, err := strconv.ParseInt(string(b), 10, 64)
23+
if err != nil {
24+
return err
25+
}
26+
27+
*d = Duration(time.Duration(i) * time.Millisecond)
28+
return nil
29+
}
30+
31+
func (d Duration) String() string {
32+
return time.Duration(d).String()
33+
}

internal/x/xtabwriter/tabwriter.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func StructValues(data interface{}) string {
2525
if shouldHideField(v.Type().Field(i)) {
2626
continue
2727
}
28-
s.WriteString(fmt.Sprintf("%s\t", v.Field(i).Interface()))
28+
s.WriteString(fmt.Sprintf("%v\t", v.Field(i).Interface()))
2929
}
3030
return s.String()
3131
}
@@ -46,6 +46,32 @@ func StructFieldNames(data interface{}) string {
4646
return s.String()
4747
}
4848

49+
// WriteTable writes the given list elements to stdout in a human readable
50+
// tabular format. Headers abide by the `tab` struct tag.
51+
//
52+
// `tab:"-"` omits the field and no tag defaults to the Go identifier.
53+
func WriteTable(length int, each func(i int) interface{}) error {
54+
if length < 1 {
55+
return nil
56+
}
57+
w := NewWriter()
58+
defer w.Flush()
59+
for ix := 0; ix < length; ix++ {
60+
item := each(ix)
61+
if ix == 0 {
62+
_, err := fmt.Fprintln(w, StructFieldNames(item))
63+
if err != nil {
64+
return err
65+
}
66+
}
67+
_, err := fmt.Fprintln(w, StructValues(item))
68+
if err != nil {
69+
return err
70+
}
71+
}
72+
return nil
73+
}
74+
4975
func shouldHideField(f reflect.StructField) bool {
5076
return f.Tag.Get(structFieldTagKey) == "-"
5177
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy