Thanks to visit codestin.com
Credit goes to github.com

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

Commit bc391d9

Browse files
authored
chore: limit direct uses of os.Stdout/os.Stderr/os.Stdin (#278)
By moving the out/err/in channels to the cobra config, it will be easier to add fast and isolated unit tests.
1 parent 3dda235 commit bc391d9

File tree

16 files changed

+78
-74
lines changed

16 files changed

+78
-74
lines changed

internal/cmd/cmd.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
package cmd
33

44
import (
5-
"os"
6-
75
"github.com/spf13/cobra"
86
"github.com/spf13/cobra/doc"
97

@@ -106,13 +104,13 @@ $ coder completion fish > ~/.config/fish/completions/coder.fish
106104
Run: func(cmd *cobra.Command, args []string) {
107105
switch args[0] {
108106
case "bash":
109-
_ = cmd.Root().GenBashCompletion(os.Stdout) // Best effort.
107+
_ = cmd.Root().GenBashCompletion(cmd.OutOrStdout()) // Best effort.
110108
case "zsh":
111-
_ = cmd.Root().GenZshCompletion(os.Stdout) // Best effort.
109+
_ = cmd.Root().GenZshCompletion(cmd.OutOrStdout()) // Best effort.
112110
case "fish":
113-
_ = cmd.Root().GenFishCompletion(os.Stdout, true) // Best effort.
111+
_ = cmd.Root().GenFishCompletion(cmd.OutOrStdout(), true) // Best effort.
114112
case "powershell":
115-
_ = cmd.Root().GenPowerShellCompletion(os.Stdout) // Best effort.
113+
_ = cmd.Root().GenPowerShellCompletion(cmd.OutOrStdout()) // Best effort.
116114
}
117115
},
118116
}

internal/cmd/envs.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"io"
99
"io/ioutil"
1010
"net/url"
11-
"os"
1211

1312
"cdr.dev/coder-cli/coder-sdk"
1413
"cdr.dev/coder-cli/internal/coderutil"
@@ -82,7 +81,7 @@ func lsEnvsCommand() *cobra.Command {
8281
return xerrors.Errorf("write table: %w", err)
8382
}
8483
case jsonOutput:
85-
err := json.NewEncoder(os.Stdout).Encode(envs)
84+
err := json.NewEncoder(cmd.OutOrStdout()).Encode(envs)
8685
if err != nil {
8786
return xerrors.Errorf("write environments as JSON: %w", err)
8887
}

internal/cmd/images.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package cmd
22

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

76
"github.com/spf13/cobra"
87
"golang.org/x/xerrors"
@@ -62,7 +61,7 @@ func lsImgsCommand(user *string) *cobra.Command {
6261

6362
switch outputFmt {
6463
case jsonOutput:
65-
enc := json.NewEncoder(os.Stdout)
64+
enc := json.NewEncoder(cmd.OutOrStdout())
6665
// pretty print the json
6766
enc.SetIndent("", "\t")
6867

internal/cmd/login.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import (
44
"bufio"
55
"context"
66
"fmt"
7+
"io"
78
"net/url"
8-
"os"
99
"strings"
1010

1111
"github.com/pkg/browser"
@@ -40,7 +40,7 @@ func loginCmd() *cobra.Command {
4040
// From this point, the commandline is correct.
4141
// Don't return errors as it would print the usage.
4242

43-
if err := login(cmd.Context(), u); err != nil {
43+
if err := login(cmd, u); err != nil {
4444
return xerrors.Errorf("login error: %w", err)
4545
}
4646
return nil
@@ -60,7 +60,7 @@ func storeConfig(envURL *url.URL, sessionToken string, urlCfg, sessionCfg config
6060
return nil
6161
}
6262

63-
func login(ctx context.Context, envURL *url.URL) error {
63+
func login(cmd *cobra.Command, envURL *url.URL) error {
6464
authURL := *envURL
6565
authURL.Path = envURL.Path + "/internal-auth"
6666
q := authURL.Query()
@@ -73,8 +73,8 @@ func login(ctx context.Context, envURL *url.URL) error {
7373
fmt.Printf("Your browser has been opened to visit:\n\n\t%s\n\n", authURL.String())
7474
}
7575

76-
token := readLine("Paste token here: ")
77-
if err := pingAPI(ctx, envURL, token); err != nil {
76+
token := readLine("Paste token here: ", cmd.InOrStdin())
77+
if err := pingAPI(cmd.Context(), envURL, token); err != nil {
7878
return xerrors.Errorf("ping API with credentials: %w", err)
7979
}
8080
if err := storeConfig(envURL, token, config.URL, config.Session); err != nil {
@@ -84,8 +84,8 @@ func login(ctx context.Context, envURL *url.URL) error {
8484
return nil
8585
}
8686

87-
func readLine(prompt string) string {
88-
reader := bufio.NewReader(os.Stdin)
87+
func readLine(prompt string, r io.Reader) string {
88+
reader := bufio.NewReader(r)
8989
fmt.Print(prompt)
9090
text, _ := reader.ReadString('\n')
9191
return strings.TrimSuffix(text, "\n")

internal/cmd/rebuild.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,13 @@ package cmd
33
import (
44
"context"
55
"fmt"
6-
"os"
76
"strings"
87
"time"
98

109
"github.com/briandowns/spinner"
1110
"github.com/fatih/color"
1211
"github.com/manifoldco/promptui"
1312
"github.com/spf13/cobra"
14-
"golang.org/x/crypto/ssh/terminal"
1513
"golang.org/x/xerrors"
1614

1715
"cdr.dev/coder-cli/coder-sdk"
@@ -85,7 +83,7 @@ func trailBuildLogs(ctx context.Context, client coder.Client, envID string) erro
8583
newSpinner := func() *spinner.Spinner { return spinner.New(spinner.CharSets[11], 100*time.Millisecond) }
8684

8785
// this tells us whether to show dynamic loaders when printing output
88-
isTerminal := terminal.IsTerminal(int(os.Stdout.Fd()))
86+
isTerminal := showInteractiveOutput
8987

9088
logs, err := client.FollowEnvironmentBuildLog(ctx, envID)
9189
if err != nil {

internal/cmd/resourcemanager.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package cmd
33
import (
44
"fmt"
55
"io"
6-
"os"
76
"sort"
87
"text/tabwriter"
98

@@ -96,7 +95,7 @@ func runResourceTop(options *resourceTopOptions) func(cmd *cobra.Command, args [
9695
return xerrors.Errorf("unknown --group %q", options.group)
9796
}
9897

99-
return printResourceTop(os.Stdout, groups, labeler, options.showEmptyGroups, options.sortBy)
98+
return printResourceTop(cmd.OutOrStdout(), groups, labeler, options.showEmptyGroups, options.sortBy)
10099
}
101100
}
102101

internal/cmd/shell.go

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ import (
2424
"cdr.dev/coder-cli/pkg/clog"
2525
)
2626

27+
var (
28+
showInteractiveOutput = terminal.IsTerminal(int(os.Stdout.Fd()))
29+
outputFd = os.Stdout.Fd()
30+
inputFd = os.Stdin.Fd()
31+
)
32+
2733
func getEnvsForCompletion(user string) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
2834
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
2935
ctx := cmd.Context()
@@ -146,15 +152,14 @@ func shell(cmd *cobra.Command, cmdArgs []string) error {
146152
}
147153

148154
// TODO: Verify this is the correct behavior
149-
isInteractive := terminal.IsTerminal(int(os.Stdout.Fd()))
150-
if isInteractive { // checkAndRebuildEnvironment requires an interactive shell
155+
if showInteractiveOutput { // checkAndRebuildEnvironment requires an interactive shell
151156
// Checks & Rebuilds the environment if needed.
152157
if err := checkAndRebuildEnvironment(ctx, client, env); err != nil {
153158
return err
154159
}
155160
}
156161

157-
if err := runCommand(ctx, client, env, command, args); err != nil {
162+
if err := runCommand(cmd, client, env, command, args); err != nil {
158163
if exitErr, ok := err.(wsep.ExitError); ok {
159164
os.Exit(exitErr.Code)
160165
}
@@ -309,26 +314,23 @@ func sendResizeEvents(ctx context.Context, termFD uintptr, process wsep.Process)
309314
}
310315
}
311316

312-
func runCommand(ctx context.Context, client coder.Client, env *coder.Environment, command string, args []string) error {
313-
termFD := os.Stdout.Fd()
314-
315-
isInteractive := terminal.IsTerminal(int(termFD))
316-
if isInteractive {
317+
func runCommand(cmd *cobra.Command, client coder.Client, env *coder.Environment, command string, args []string) error {
318+
if showInteractiveOutput {
317319
// If the client has a tty, take over it by setting the raw mode.
318320
// This allows for all input to be directly forwarded to the remote process,
319321
// otherwise, the local terminal would buffer input, interpret special keys, etc.
320-
stdinState, err := xterminal.MakeRaw(os.Stdin.Fd())
322+
stdinState, err := xterminal.MakeRaw(inputFd)
321323
if err != nil {
322324
return err
323325
}
324326
defer func() {
325327
// Best effort. If this fails it will result in a broken terminal,
326328
// but there is nothing we can do about it.
327-
_ = xterminal.Restore(os.Stdin.Fd(), stdinState)
329+
_ = xterminal.Restore(inputFd, stdinState)
328330
}()
329331
}
330332

331-
ctx, cancel := context.WithCancel(ctx)
333+
ctx, cancel := context.WithCancel(cmd.Context())
332334
defer cancel()
333335

334336
conn, err := coderutil.DialEnvWsep(ctx, client, env)
@@ -338,7 +340,7 @@ func runCommand(ctx context.Context, client coder.Client, env *coder.Environment
338340
go heartbeat(ctx, conn, 15*time.Second)
339341

340342
var cmdEnv []string
341-
if isInteractive {
343+
if showInteractiveOutput {
342344
term := os.Getenv("TERM")
343345
if term == "" {
344346
term = "xterm"
@@ -350,7 +352,7 @@ func runCommand(ctx context.Context, client coder.Client, env *coder.Environment
350352
process, err := execer.Start(ctx, wsep.Command{
351353
Command: command,
352354
Args: args,
353-
TTY: isInteractive,
355+
TTY: showInteractiveOutput,
354356
Stdin: true,
355357
Env: cmdEnv,
356358
})
@@ -363,8 +365,8 @@ func runCommand(ctx context.Context, client coder.Client, env *coder.Environment
363365
}
364366

365367
// Now that the remote process successfully started, if we have a tty, start the resize event watcher.
366-
if isInteractive {
367-
go sendResizeEvents(ctx, termFD, process)
368+
if showInteractiveOutput {
369+
go sendResizeEvents(ctx, outputFd, process)
368370
}
369371

370372
go func() {
@@ -373,17 +375,17 @@ func runCommand(ctx context.Context, client coder.Client, env *coder.Environment
373375

374376
ap := activity.NewPusher(client, env.ID, sshActivityName)
375377
wr := ap.Writer(stdin)
376-
if _, err := io.Copy(wr, os.Stdin); err != nil {
378+
if _, err := io.Copy(wr, cmd.InOrStdin()); err != nil {
377379
cancel()
378380
}
379381
}()
380382
go func() {
381-
if _, err := io.Copy(os.Stdout, process.Stdout()); err != nil {
383+
if _, err := io.Copy(cmd.OutOrStdout(), process.Stdout()); err != nil {
382384
cancel()
383385
}
384386
}()
385387
go func() {
386-
if _, err := io.Copy(os.Stderr, process.Stderr()); err != nil {
388+
if _, err := io.Copy(cmd.ErrOrStderr(), process.Stderr()); err != nil {
387389
cancel()
388390
}
389391
}()

internal/cmd/sync.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,15 @@ func makeRunSync(init *bool) func(cmd *cobra.Command, args []string) error {
9090
}
9191

9292
s := sync.Sync{
93-
Init: *init,
94-
Env: *env,
95-
RemoteDir: remoteDir,
96-
LocalDir: absLocal,
97-
Client: client,
93+
Init: *init,
94+
Env: *env,
95+
RemoteDir: remoteDir,
96+
LocalDir: absLocal,
97+
Client: client,
98+
OutW: cmd.OutOrStdout(),
99+
ErrW: cmd.ErrOrStderr(),
100+
InputReader: cmd.InOrStdin(),
101+
IsInteractiveOutput: showInteractiveOutput,
98102
}
99103

100104
localVersion := rsyncVersion()

internal/cmd/tags.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package cmd
22

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

76
"github.com/spf13/cobra"
87
"golang.org/x/xerrors"
@@ -113,7 +112,7 @@ func tagsLsCmd() *cobra.Command {
113112
return err
114113
}
115114
case jsonOutput:
116-
err := json.NewEncoder(os.Stdout).Encode(tags)
115+
err := json.NewEncoder(cmd.OutOrStdout()).Encode(tags)
117116
if err != nil {
118117
return err
119118
}

internal/cmd/tokens.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package cmd
33
import (
44
"encoding/json"
55
"fmt"
6-
"os"
76

87
"github.com/spf13/cobra"
98
"golang.org/x/xerrors"
@@ -56,7 +55,7 @@ func lsTokensCmd() *cobra.Command {
5655
return xerrors.Errorf("write table: %w", err)
5756
}
5857
case jsonOutput:
59-
err := json.NewEncoder(os.Stdout).Encode(tokens)
58+
err := json.NewEncoder(cmd.OutOrStdout()).Encode(tokens)
6059
if err != nil {
6160
return xerrors.Errorf("write tokens as JSON: %w", err)
6261
}

internal/cmd/urls.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7-
"os"
87
"regexp"
98
"strconv"
109
"strings"
@@ -107,7 +106,7 @@ func listDevURLsCmd(outputFmt *string) func(cmd *cobra.Command, args []string) e
107106
return xerrors.Errorf("write table: %w", err)
108107
}
109108
case jsonOutput:
110-
if err := json.NewEncoder(os.Stdout).Encode(devURLs); err != nil {
109+
if err := json.NewEncoder(cmd.OutOrStdout()).Encode(devURLs); err != nil {
111110
return xerrors.Errorf("encode DevURLs as json: %w", err)
112111
}
113112
default:

internal/cmd/users.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package cmd
22

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

76
"github.com/spf13/cobra"
87
"golang.org/x/xerrors"
@@ -51,7 +50,7 @@ func listUsers(outputFmt *string) func(cmd *cobra.Command, args []string) error
5150
return xerrors.Errorf("write table: %w", err)
5251
}
5352
case "json":
54-
if err := json.NewEncoder(os.Stdout).Encode(users); err != nil {
53+
if err := json.NewEncoder(cmd.OutOrStdout()).Encode(users); err != nil {
5554
return xerrors.Errorf("encode users as json: %w", err)
5655
}
5756
default:

0 commit comments

Comments
 (0)