Skip to content

Commit 6b0c76d

Browse files
committed
Fix Windows support
1 parent 1c753dc commit 6b0c76d

File tree

10 files changed

+76
-54
lines changed

10 files changed

+76
-54
lines changed

agent/agent.go

Lines changed: 39 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -56,24 +56,24 @@ type agent struct {
5656
sshServer *ssh.Server
5757
}
5858

59-
func (s *agent) run(ctx context.Context) {
59+
func (a *agent) run(ctx context.Context) {
6060
var peerListener *peerbroker.Listener
6161
var err error
6262
// An exponential back-off occurs when the connection is failing to dial.
6363
// This is to prevent server spam in case of a coderd outage.
6464
for retrier := retry.New(50*time.Millisecond, 10*time.Second); retrier.Wait(ctx); {
65-
peerListener, err = s.clientDialer(ctx, s.options)
65+
peerListener, err = a.clientDialer(ctx, a.options)
6666
if err != nil {
6767
if errors.Is(err, context.Canceled) {
6868
return
6969
}
70-
if s.isClosed() {
70+
if a.isClosed() {
7171
return
7272
}
73-
s.options.Logger.Warn(context.Background(), "failed to dial", slog.Error(err))
73+
a.options.Logger.Warn(context.Background(), "failed to dial", slog.Error(err))
7474
continue
7575
}
76-
s.options.Logger.Info(context.Background(), "connected")
76+
a.options.Logger.Info(context.Background(), "connected")
7777
break
7878
}
7979
select {
@@ -85,48 +85,48 @@ func (s *agent) run(ctx context.Context) {
8585
for {
8686
conn, err := peerListener.Accept()
8787
if err != nil {
88-
if s.isClosed() {
88+
if a.isClosed() {
8989
return
9090
}
91-
s.options.Logger.Debug(ctx, "peer listener accept exited; restarting connection", slog.Error(err))
92-
s.run(ctx)
91+
a.options.Logger.Debug(ctx, "peer listener accept exited; restarting connection", slog.Error(err))
92+
a.run(ctx)
9393
return
9494
}
95-
s.closeMutex.Lock()
96-
s.connCloseWait.Add(1)
97-
s.closeMutex.Unlock()
98-
go s.handlePeerConn(ctx, conn)
95+
a.closeMutex.Lock()
96+
a.connCloseWait.Add(1)
97+
a.closeMutex.Unlock()
98+
go a.handlePeerConn(ctx, conn)
9999
}
100100
}
101101

102-
func (s *agent) handlePeerConn(ctx context.Context, conn *peer.Conn) {
102+
func (a *agent) handlePeerConn(ctx context.Context, conn *peer.Conn) {
103103
go func() {
104104
<-conn.Closed()
105-
s.connCloseWait.Done()
105+
a.connCloseWait.Done()
106106
}()
107107
for {
108108
channel, err := conn.Accept(ctx)
109109
if err != nil {
110-
if errors.Is(err, peer.ErrClosed) || s.isClosed() {
110+
if errors.Is(err, peer.ErrClosed) || a.isClosed() {
111111
return
112112
}
113-
s.options.Logger.Debug(ctx, "accept channel from peer connection", slog.Error(err))
113+
a.options.Logger.Debug(ctx, "accept channel from peer connection", slog.Error(err))
114114
return
115115
}
116116

117117
switch channel.Protocol() {
118118
case "ssh":
119-
s.sshServer.HandleConn(channel.NetConn())
119+
a.sshServer.HandleConn(channel.NetConn())
120120
default:
121-
s.options.Logger.Warn(ctx, "unhandled protocol from channel",
121+
a.options.Logger.Warn(ctx, "unhandled protocol from channel",
122122
slog.F("protocol", channel.Protocol()),
123123
slog.F("label", channel.Label()),
124124
)
125125
}
126126
}
127127
}
128128

129-
func (s *agent) init(ctx context.Context) {
129+
func (a *agent) init(ctx context.Context) {
130130
// Clients' should ignore the host key when connecting.
131131
// The agent needs to authenticate with coderd to SSH,
132132
// so SSH authentication doesn't improve security.
@@ -138,17 +138,17 @@ func (s *agent) init(ctx context.Context) {
138138
if err != nil {
139139
panic(err)
140140
}
141-
sshLogger := s.options.Logger.Named("ssh-server")
141+
sshLogger := a.options.Logger.Named("ssh-server")
142142
forwardHandler := &ssh.ForwardedTCPHandler{}
143-
s.sshServer = &ssh.Server{
143+
a.sshServer = &ssh.Server{
144144
ChannelHandlers: ssh.DefaultChannelHandlers,
145145
ConnectionFailedCallback: func(conn net.Conn, err error) {
146146
sshLogger.Info(ctx, "ssh connection ended", slog.Error(err))
147147
},
148148
Handler: func(session ssh.Session) {
149-
err := s.handleSSHSession(session)
149+
err := a.handleSSHSession(session)
150150
if err != nil {
151-
s.options.Logger.Warn(ctx, "ssh session failed", slog.Error(err))
151+
a.options.Logger.Warn(ctx, "ssh session failed", slog.Error(err))
152152
_ = session.Exit(1)
153153
return
154154
}
@@ -182,10 +182,10 @@ func (s *agent) init(ctx context.Context) {
182182
},
183183
}
184184

185-
go s.run(ctx)
185+
go a.run(ctx)
186186
}
187187

188-
func (*agent) handleSSHSession(session ssh.Session) error {
188+
func (a *agent) handleSSHSession(session ssh.Session) error {
189189
var (
190190
command string
191191
args = []string{}
@@ -240,7 +240,10 @@ func (*agent) handleSSHSession(session ssh.Session) error {
240240
}
241241
go func() {
242242
for win := range windowSize {
243-
_ = ptty.Resize(uint16(win.Width), uint16(win.Height))
243+
err = ptty.Resize(uint16(win.Width), uint16(win.Height))
244+
if err != nil {
245+
a.options.Logger.Warn(context.Background(), "failed to resize tty", slog.Error(err))
246+
}
244247
}
245248
}()
246249
go func() {
@@ -274,24 +277,24 @@ func (*agent) handleSSHSession(session ssh.Session) error {
274277
}
275278

276279
// isClosed returns whether the API is closed or not.
277-
func (s *agent) isClosed() bool {
280+
func (a *agent) isClosed() bool {
278281
select {
279-
case <-s.closed:
282+
case <-a.closed:
280283
return true
281284
default:
282285
return false
283286
}
284287
}
285288

286-
func (s *agent) Close() error {
287-
s.closeMutex.Lock()
288-
defer s.closeMutex.Unlock()
289-
if s.isClosed() {
289+
func (a *agent) Close() error {
290+
a.closeMutex.Lock()
291+
defer a.closeMutex.Unlock()
292+
if a.isClosed() {
290293
return nil
291294
}
292-
close(s.closed)
293-
s.closeCancel()
294-
_ = s.sshServer.Close()
295-
s.connCloseWait.Wait()
295+
close(a.closed)
296+
a.closeCancel()
297+
_ = a.sshServer.Close()
298+
a.connCloseWait.Wait()
296299
return nil
297300
}

cli/cliui/prompt.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cliui
22

33
import (
44
"bufio"
5+
"bytes"
56
"encoding/json"
67
"fmt"
78
"io"
@@ -62,7 +63,11 @@ func Prompt(cmd *cobra.Command, opts PromptOptions) (string, error) {
6263
var rawMessage json.RawMessage
6364
err := json.NewDecoder(pipeReader).Decode(&rawMessage)
6465
if err == nil {
65-
line = string(rawMessage)
66+
var buf bytes.Buffer
67+
err = json.Compact(&buf, rawMessage)
68+
if err == nil {
69+
line = buf.String()
70+
}
6671
}
6772
}
6873
}

cli/cliui/prompt_test.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,7 @@ func TestPrompt(t *testing.T) {
9393
ptty.WriteLine(`{
9494
"test": "wow"
9595
}`)
96-
require.Equal(t, `{
97-
"test": "wow"
98-
}`, <-doneChan)
96+
require.Equal(t, `{"test":"wow"}`, <-doneChan)
9997
})
10098
}
10199

cli/configssh.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ func configSSH() *cobra.Command {
109109
if err != nil {
110110
return err
111111
}
112-
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "An auto-generated ssh config was written to \"%s\"\n", sshConfigFile)
112+
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "An auto-generated ssh config was written to %q\n", sshConfigFile)
113113
_, _ = fmt.Fprintln(cmd.OutOrStdout(), "You should now be able to ssh into your workspace")
114114
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "For example, try running\n\n\t$ ssh coder.%s\n\n", workspaces[0].Name)
115115
return nil

cli/ssh.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ import (
1111
"github.com/pion/webrtc/v3"
1212
"github.com/spf13/cobra"
1313
gossh "golang.org/x/crypto/ssh"
14-
"golang.org/x/crypto/ssh/terminal"
1514
"golang.org/x/xerrors"
1615

1716
"github.com/coder/coder/cli/cliflag"
1817
"github.com/coder/coder/cli/cliui"
1918
"github.com/coder/coder/coderd/database"
2019
"github.com/coder/coder/codersdk"
20+
"golang.org/x/crypto/ssh/terminal"
2121
)
2222

2323
func ssh() *cobra.Command {
@@ -121,18 +121,16 @@ func ssh() *cobra.Command {
121121
}
122122

123123
if isatty.IsTerminal(os.Stdout.Fd()) {
124-
state, err := terminal.MakeRaw(int(os.Stdout.Fd()))
124+
state, err := terminal.MakeRaw(int(os.Stdin.Fd()))
125125
if err != nil {
126126
return err
127127
}
128128
defer func() {
129-
_ = terminal.Restore(int(os.Stdout.Fd()), state)
129+
_ = terminal.Restore(int(os.Stdin.Fd()), state)
130130
}()
131131
}
132132

133-
err = sshSession.RequestPty("xterm-256color", 128, 128, gossh.TerminalModes{
134-
gossh.OCRNL: 1,
135-
})
133+
err = sshSession.RequestPty("xterm-256color", 128, 128, gossh.TerminalModes{})
136134
if err != nil {
137135
return err
138136
}

cli/start.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ func start() *cobra.Command {
314314
cliflag.StringVarP(root.Flags(), &accessURL, "access-url", "", "CODER_ACCESS_URL", "", "Specifies the external URL to access Coder")
315315
cliflag.StringVarP(root.Flags(), &address, "address", "a", "CODER_ADDRESS", "127.0.0.1:3000", "The address to serve the API and dashboard")
316316
// systemd uses the CACHE_DIRECTORY environment variable!
317-
cliflag.StringVarP(root.Flags(), &cacheDir, "cache-dir", "", "CACHE_DIRECTORY", filepath.Join(os.TempDir(), ".coder-cache"), "Specifies a directory to cache binaries for provision operations.")
317+
cliflag.StringVarP(root.Flags(), &cacheDir, "cache-dir", "", "CACHE_DIRECTORY", filepath.Join(os.TempDir(), "coder-cache"), "Specifies a directory to cache binaries for provision operations.")
318318
cliflag.BoolVarP(root.Flags(), &dev, "dev", "", "CODER_DEV_MODE", false, "Serve Coder in dev mode for tinkering")
319319
cliflag.StringVarP(root.Flags(), &postgresURL, "postgres-url", "", "CODER_PG_CONNECTION_URL", "", "URL of a PostgreSQL database to connect to")
320320
cliflag.Uint8VarP(root.Flags(), &provisionerDaemonCount, "provisioner-daemons", "", "CODER_PROVISIONER_DAEMONS", 1, "The amount of provisioner daemons to create on start.")
@@ -370,6 +370,11 @@ func createFirstUser(cmd *cobra.Command, client *codersdk.Client, cfg config.Roo
370370
}
371371

372372
func newProvisionerDaemon(ctx context.Context, client *codersdk.Client, logger slog.Logger, cacheDir string) (*provisionerd.Server, error) {
373+
err := os.MkdirAll(cacheDir, 0700)
374+
if err != nil {
375+
return nil, xerrors.Errorf("mkdir %q: %w", cacheDir, err)
376+
}
377+
373378
terraformClient, terraformServer := provisionersdk.TransportPipe()
374379
go func() {
375380
err := terraform.Serve(ctx, &terraform.ServeOptions{

examples/gcp-linux/main.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Coder requires a Google Cloud Service Account to provision workspaces.
1818
- Service Account User
1919
3. Click on the created key, and navigate to the "Keys" tab.
2020
4. Click "Add key", then "Create new key".
21-
5. Generate a JSON private key, and paste the contents in \'\' quotes below.
21+
5. Generate a JSON private key, and paste the contents below.
2222
EOF
2323
sensitive = true
2424
}

examples/gcp-windows/main.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Coder requires a Google Cloud Service Account to provision workspaces.
1818
- Service Account User
1919
3. Click on the created key, and navigate to the "Keys" tab.
2020
4. Click "Add key", then "Create new key".
21-
5. Generate a JSON private key, and paste the contents in \'\' quotes below.
21+
5. Generate a JSON private key, and paste the contents below.
2222
EOF
2323
sensitive = true
2424
}

provisioner/terraform/provision.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"os"
1010
"os/exec"
1111
"path/filepath"
12+
"runtime"
1213
"strings"
1314

1415
"github.com/awalterschulze/gographviz"
@@ -87,7 +88,9 @@ func (t *terraform) Provision(stream proto.DRPCProvisioner_ProvisionStream) erro
8788
})
8889
}
8990
}()
90-
if t.cachePath != "" {
91+
// Windows doesn't work with a plugin cache directory.
92+
// The cause is unknown, but it should work.
93+
if t.cachePath != "" && runtime.GOOS != "windows" {
9194
err = terraform.SetEnv(map[string]string{
9295
"TF_PLUGIN_CACHE_DIR": t.cachePath,
9396
})

provisioner/terraform/serve.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@ package terraform
22

33
import (
44
"context"
5-
"os/exec"
5+
"path/filepath"
66

77
"github.com/hashicorp/go-version"
88
"golang.org/x/xerrors"
99

1010
"cdr.dev/slog"
1111

12+
"github.com/cli/safeexec"
1213
"github.com/coder/coder/provisionersdk"
1314

1415
"github.com/hashicorp/hc-install/product"
@@ -41,7 +42,7 @@ type ServeOptions struct {
4142
// Serve starts a dRPC server on the provided transport speaking Terraform provisioner.
4243
func Serve(ctx context.Context, options *ServeOptions) error {
4344
if options.BinaryPath == "" {
44-
binaryPath, err := exec.LookPath("terraform")
45+
binaryPath, err := safeexec.LookPath("terraform")
4546
if err != nil {
4647
installer := &releases.ExactVersion{
4748
InstallDir: options.CachePath,
@@ -55,7 +56,16 @@ func Serve(ctx context.Context, options *ServeOptions) error {
5556
}
5657
options.BinaryPath = execPath
5758
} else {
58-
options.BinaryPath = binaryPath
59+
// If the "coder" binary is in the same directory as
60+
// the "terraform" binary, "terraform" is returned.
61+
//
62+
// We must resolve the absolute path for other processes
63+
// to execute this properly!
64+
absoluteBinary, err := filepath.Abs(binaryPath)
65+
if err != nil {
66+
return xerrors.Errorf("absolute: %w", err)
67+
}
68+
options.BinaryPath = absoluteBinary
5969
}
6070
}
6171
return provisionersdk.Serve(ctx, &terraform{

0 commit comments

Comments
 (0)