Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
This repository was archived by the owner on Aug 30, 2024. It is now read-only.
/coder-v1-cliPublic archive

Commit3536869

Browse files
kylecarbsjawnsy
andauthored
feat: Display error to user when SSHing into offline workspace (#411)
* feat: Display error to user when SSH'ing into offline workspace* Remove new \r* Update internal/cmd/tunnel.goCo-authored-by: Jonathan Yu <jonathan@coder.com>Co-authored-by: Jonathan Yu <jonathan@coder.com>
1 parent3af8385 commit3536869

File tree

3 files changed

+125
-30
lines changed

3 files changed

+125
-30
lines changed

‎go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,10 @@ require (
2525
github.com/rjeczalik/notifyv0.9.2
2626
github.com/spf13/cobrav1.2.1
2727
github.com/stretchr/testifyv1.7.0
28+
golang.org/x/cryptov0.0.0-20210711020723-a769d52b0f97
2829
golang.org/x/netv0.0.0-20210614182718-04defd469f4e
2930
golang.org/x/syncv0.0.0-20210220032951-036812b2e83c
30-
golang.org/x/sysv0.0.0-20210514084401-e8d321eab015
31+
golang.org/x/sysv0.0.0-20210615035016-665e8c7367d1
3132
golang.org/x/termv0.0.0-20201126162022-7de9c90e9dd1
3233
golang.org/x/timev0.0.0-20191024005414-555d28b269f0
3334
golang.org/x/xerrorsv0.0.0-20200804184101-5ec99f83aff1

‎go.sum

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -430,8 +430,9 @@ golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8U
430430
golang.org/x/cryptov0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
431431
golang.org/x/cryptov0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
432432
golang.org/x/cryptov0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
433-
golang.org/x/cryptov0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
434433
golang.org/x/cryptov0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
434+
golang.org/x/cryptov0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
435+
golang.org/x/cryptov0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
435436
golang.org/x/expv0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
436437
golang.org/x/expv0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
437438
golang.org/x/expv0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -590,8 +591,9 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
590591
golang.org/x/sysv0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
591592
golang.org/x/sysv0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
592593
golang.org/x/sysv0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
593-
golang.org/x/sysv0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E=
594594
golang.org/x/sysv0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
595+
golang.org/x/sysv0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
596+
golang.org/x/sysv0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
595597
golang.org/x/termv0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
596598
golang.org/x/termv0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
597599
golang.org/x/textv0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

‎internal/cmd/tunnel.go

Lines changed: 119 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ import (
1212

1313
"cdr.dev/slog"
1414
"cdr.dev/slog/sloggers/sloghuman"
15+
"github.com/fatih/color"
1516
"github.com/pion/webrtc/v3"
1617
"github.com/spf13/cobra"
18+
"golang.org/x/crypto/ssh"
1719
"golang.org/x/xerrors"
1820

1921
"cdr.dev/coder-cli/coder-sdk"
2022
"cdr.dev/coder-cli/internal/x/xcobra"
23+
"cdr.dev/coder-cli/pkg/clog"
2124
"cdr.dev/coder-cli/wsnet"
2225
)
2326

@@ -59,20 +62,34 @@ coder tunnel my-dev 3000 3000
5962
}
6063
baseURL:=sdk.BaseURL()
6164

62-
workspaces,err:=getWorkspaces(ctx,sdk,coder.Me)
65+
workspace,err:=findWorkspace(ctx,sdk,args[0],coder.Me)
6366
iferr!=nil {
6467
returnxerrors.Errorf("get workspaces: %w",err)
6568
}
6669

67-
varworkspaceIDstring
68-
for_,workspace:=rangeworkspaces {
69-
ifworkspace.Name==args[0] {
70-
workspaceID=workspace.ID
71-
break
70+
ifworkspace.LatestStat.ContainerStatus!=coder.WorkspaceOn {
71+
color.NoColor=false
72+
notAvailableError:=clog.Error("workspace not available",
73+
fmt.Sprintf("current status: %q",workspace.LatestStat.ContainerStatus),
74+
clog.BlankLine,
75+
clog.Tipf("use\"coder workspaces rebuild %s\" to rebuild this workspace",workspace.Name),
76+
)
77+
// If we're attempting to forward our remote SSH port,
78+
// we want to communicate with the OpenSSH protocol so
79+
// SSH clients can properly display output to our users.
80+
ifremotePort==12213 {
81+
rawKey,err:=sdk.SSHKey(ctx)
82+
iferr!=nil {
83+
returnxerrors.Errorf("get ssh key: %w",err)
84+
}
85+
err=discardSSHConnection(&stdioConn{},rawKey.PrivateKey,notAvailableError.String())
86+
iferr!=nil {
87+
returnerr
88+
}
89+
returnnil
7290
}
73-
}
74-
ifworkspaceID=="" {
75-
returnxerrors.Errorf("No workspace found by name '%s'",args[0])
91+
92+
returnnotAvailableError
7693
}
7794

7895
iceServers,err:=sdk.ICEServers(ctx)
@@ -82,14 +99,14 @@ coder tunnel my-dev 3000 3000
8299
log.Debug(ctx,"got ICE servers",slog.F("ice",iceServers))
83100

84101
c:=&tunnneler{
85-
log:log,
86-
brokerAddr:&baseURL,
87-
token:sdk.Token(),
88-
workspaceID:workspaceID,
89-
iceServers:iceServers,
90-
stdio:args[2]=="stdio",
91-
localPort:uint16(localPort),
92-
remotePort:uint16(remotePort),
102+
log:log,
103+
brokerAddr:&baseURL,
104+
token:sdk.Token(),
105+
workspace:workspace,
106+
iceServers:iceServers,
107+
stdio:args[2]=="stdio",
108+
localPort:uint16(localPort),
109+
remotePort:uint16(remotePort),
93110
}
94111

95112
err=c.start(ctx)
@@ -105,14 +122,14 @@ coder tunnel my-dev 3000 3000
105122
}
106123

107124
typetunnnelerstruct {
108-
logslog.Logger
109-
brokerAddr*url.URL
110-
tokenstring
111-
workspaceIDstring
112-
iceServers[]webrtc.ICEServer
113-
remotePortuint16
114-
localPortuint16
115-
stdiobool
125+
log slog.Logger
126+
brokerAddr*url.URL
127+
tokenstring
128+
workspace*coder.Workspace
129+
iceServers []webrtc.ICEServer
130+
remotePortuint16
131+
localPortuint16
132+
stdiobool
116133
}
117134

118135
func (c*tunnneler)start(ctx context.Context)error {
@@ -121,7 +138,7 @@ func (c *tunnneler) start(ctx context.Context) error {
121138
dialLog:=c.log.Named("wsnet")
122139
wd,err:=wsnet.DialWebsocket(
123140
ctx,
124-
wsnet.ConnectEndpoint(c.brokerAddr,c.workspaceID,c.token),
141+
wsnet.ConnectEndpoint(c.brokerAddr,c.workspace.ID,c.token),
125142
&wsnet.DialOptions{
126143
Log:&dialLog,
127144
TURNProxyAuthToken:c.token,
@@ -156,7 +173,7 @@ func (c *tunnneler) start(ctx context.Context) error {
156173
return
157174
case<-ticker.C:
158175
// silently ignore failures so we don't spam the console
159-
_=sdk.UpdateLastConnectionAt(ctx,c.workspaceID)
176+
_=sdk.UpdateLastConnectionAt(ctx,c.workspace.ID)
160177
}
161178
}
162179
}()
@@ -203,3 +220,78 @@ func (c *tunnneler) start(ctx context.Context) error {
203220
}()
204221
}
205222
}
223+
224+
// Used to treat stdio like a connection for proxying SSH.
225+
typestdioConnstruct{}
226+
227+
func (s*stdioConn)Read(b []byte) (nint,errerror) {
228+
returnos.Stdin.Read(b)
229+
}
230+
231+
func (s*stdioConn)Write(b []byte) (nint,errerror) {
232+
returnos.Stdout.Write(b)
233+
}
234+
235+
func (s*stdioConn)Close()error {
236+
returnnil
237+
}
238+
239+
func (s*stdioConn)LocalAddr() net.Addr {
240+
returnnil
241+
}
242+
243+
func (s*stdioConn)RemoteAddr() net.Addr {
244+
returnnil
245+
}
246+
247+
func (s*stdioConn)SetDeadline(t time.Time)error {
248+
returnnil
249+
}
250+
251+
func (s*stdioConn)SetReadDeadline(t time.Time)error {
252+
returnnil
253+
}
254+
255+
func (s*stdioConn)SetWriteDeadline(t time.Time)error {
256+
returnnil
257+
}
258+
259+
// discardSSHConnection accepts a connection then outputs the message provided
260+
// to any channel opened, immediately closing the connection afterwards.
261+
//
262+
// Used to provide status to connecting clients while still aligning with the
263+
// native SSH protocol.
264+
funcdiscardSSHConnection(nc net.Conn,privateKeystring,msgstring)error {
265+
config:=&ssh.ServerConfig{
266+
NoClientAuth:true,
267+
}
268+
key,err:=ssh.ParseRawPrivateKey([]byte(privateKey))
269+
iferr!=nil {
270+
returnfmt.Errorf("parse private key: %w",err)
271+
}
272+
signer,err:=ssh.NewSignerFromKey(key)
273+
iferr!=nil {
274+
returnfmt.Errorf("signer from private key: %w",err)
275+
}
276+
config.AddHostKey(signer)
277+
conn,chans,reqs,err:=ssh.NewServerConn(nc,config)
278+
iferr!=nil {
279+
returnfmt.Errorf("create server conn: %w",err)
280+
}
281+
gossh.DiscardRequests(reqs)
282+
ch,req,err:= (<-chans).Accept()
283+
iferr!=nil {
284+
returnfmt.Errorf("accept channel: %w",err)
285+
}
286+
gossh.DiscardRequests(req)
287+
288+
_,err=ch.Write([]byte(msg))
289+
iferr!=nil {
290+
returnfmt.Errorf("write channel: %w",err)
291+
}
292+
err=ch.Close()
293+
iferr!=nil {
294+
returnfmt.Errorf("close channel: %w",err)
295+
}
296+
returnconn.Close()
297+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp