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

Commitfc3e193

Browse files
committed
feat(cli): add experimental rpty command
1 parent097fd85 commitfc3e193

File tree

2 files changed

+211
-0
lines changed

2 files changed

+211
-0
lines changed

‎cli/exp.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ func (r *RootCmd) expCmd() *serpent.Command {
1414
r.scaletestCmd(),
1515
r.errorExample(),
1616
r.promptExample(),
17+
r.rptyCommand(),
1718
},
1819
}
1920
returncmd

‎cli/exp_rpty.go

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
package cli
2+
3+
import (
4+
"bufio"
5+
"context"
6+
"encoding/json"
7+
"io"
8+
"os"
9+
"strings"
10+
11+
"github.com/google/uuid"
12+
"github.com/mattn/go-isatty"
13+
"golang.org/x/term"
14+
"golang.org/x/xerrors"
15+
16+
"github.com/coder/coder/v2/cli/cliui"
17+
"github.com/coder/coder/v2/codersdk"
18+
"github.com/coder/coder/v2/codersdk/workspacesdk"
19+
"github.com/coder/coder/v2/pty"
20+
"github.com/coder/serpent"
21+
)
22+
23+
func (r*RootCmd)rptyCommand()*serpent.Command {
24+
var (
25+
client=new(codersdk.Client)
26+
argshandleRPTYArgs
27+
)
28+
29+
cmd:=&serpent.Command{
30+
Handler:func(inv*serpent.Invocation)error {
31+
ifr.disableDirect {
32+
returnxerrors.New("direct connections are disabled, but you can try websocat ;-)")
33+
}
34+
args.NamedWorkspace=inv.Args[0]
35+
args.Command=inv.Args[1:]
36+
returnhandleRPTY(inv,client,args)
37+
},
38+
Long:"Establish an RPTY session with a workspace/agent. This uses the same mechanism as the Web Terminal.",
39+
Middleware:serpent.Chain(
40+
serpent.RequireRangeArgs(1,-1),
41+
r.InitClient(client),
42+
),
43+
Options: []serpent.Option{
44+
{
45+
Name:"container",
46+
Description:"The container name or ID to connect to.",
47+
Flag:"container",
48+
FlagShorthand:"c",
49+
Default:"",
50+
Value:serpent.StringOf(&args.Container),
51+
},
52+
{
53+
Name:"container-user",
54+
Description:"The user to connect as.",
55+
Flag:"container-user",
56+
FlagShorthand:"u",
57+
Default:"",
58+
Value:serpent.StringOf(&args.ContainerUser),
59+
},
60+
{
61+
Name:"reconnect",
62+
Description:"The reconnect ID to use.",
63+
Flag:"reconnect",
64+
FlagShorthand:"r",
65+
Default:"",
66+
Value:serpent.StringOf(&args.ReconnectID),
67+
},
68+
},
69+
Short:"Establish an RPTY session with a workspace/agent.",
70+
Use:"rpty",
71+
}
72+
73+
returncmd
74+
}
75+
76+
typehandleRPTYArgsstruct {
77+
Command []string
78+
Containerstring
79+
ContainerUserstring
80+
NamedWorkspacestring
81+
ReconnectIDstring
82+
}
83+
84+
funchandleRPTY(inv*serpent.Invocation,client*codersdk.Client,argshandleRPTYArgs)error {
85+
ctx,cancel:=context.WithCancel(inv.Context())
86+
defercancel()
87+
88+
varreconnectID uuid.UUID
89+
ifargs.ReconnectID!="" {
90+
rid,err:=uuid.Parse(args.ReconnectID)
91+
iferr!=nil {
92+
returnxerrors.Errorf("invalid reconnect ID: %w",err)
93+
}
94+
reconnectID=rid
95+
}else {
96+
reconnectID=uuid.New()
97+
}
98+
ws,agt,err:=getWorkspaceAndAgent(ctx,inv,client,true,args.NamedWorkspace)
99+
iferr!=nil {
100+
returnerr
101+
}
102+
103+
varctIDstring
104+
ifargs.Container!="" {
105+
cts,err:=client.WorkspaceAgentListContainers(ctx,agt.ID,nil)
106+
iferr!=nil {
107+
returnerr
108+
}
109+
for_,ct:=rangects.Containers {
110+
ifct.FriendlyName==args.Container||ct.ID==args.Container {
111+
ctID=ct.ID
112+
break
113+
}
114+
}
115+
ifctID=="" {
116+
returnxerrors.Errorf("container %q not found",args.Container)
117+
}
118+
}
119+
120+
iferr:=cliui.Agent(ctx,inv.Stderr,agt.ID, cliui.AgentOptions{
121+
FetchInterval:0,
122+
Fetch:client.WorkspaceAgent,
123+
FetchLogs:client.WorkspaceAgentLogsAfter,
124+
Wait:false,
125+
});err!=nil {
126+
returnerr
127+
}
128+
129+
// Get the width and height of the terminal.
130+
vartermWidth,termHeightuint16
131+
stdoutFile,validOut:=inv.Stdout.(*os.File)
132+
ifvalidOut&&isatty.IsTerminal(stdoutFile.Fd()) {
133+
w,h,err:=term.GetSize(int(stdoutFile.Fd()))
134+
iferr==nil {
135+
//nolint: gosec
136+
termWidth,termHeight=uint16(w),uint16(h)
137+
}
138+
}
139+
140+
// Set stdin to raw mode so that control characters work.
141+
stdinFile,validIn:=inv.Stdin.(*os.File)
142+
ifvalidIn&&isatty.IsTerminal(stdinFile.Fd()) {
143+
inState,err:=pty.MakeInputRaw(stdinFile.Fd())
144+
iferr!=nil {
145+
returnxerrors.Errorf("failed to set input terminal to raw mode: %w",err)
146+
}
147+
deferfunc() {
148+
_=pty.RestoreTerminal(stdinFile.Fd(),inState)
149+
}()
150+
}
151+
152+
conn,err:=workspacesdk.New(client).AgentReconnectingPTY(ctx, workspacesdk.WorkspaceAgentReconnectingPTYOpts{
153+
AgentID:agt.ID,
154+
Reconnect:reconnectID,
155+
Command:strings.Join(args.Command," "),
156+
Container:ctID,
157+
ContainerUser:args.ContainerUser,
158+
Width:termWidth,
159+
Height:termHeight,
160+
})
161+
iferr!=nil {
162+
returnxerrors.Errorf("open reconnecting PTY: %w",err)
163+
}
164+
deferconn.Close()
165+
166+
cliui.Infof(inv.Stderr,"Connected to %s (agent id: %s)",args.NamedWorkspace,agt.ID)
167+
closeUsage:=client.UpdateWorkspaceUsageWithBodyContext(ctx,ws.ID, codersdk.PostWorkspaceUsageRequest{
168+
AgentID:agt.ID,
169+
AppName:codersdk.UsageAppNameReconnectingPty,
170+
})
171+
defercloseUsage()
172+
173+
stdinDone:=make(chanstruct{})
174+
stdoutDone:=make(chanstruct{})
175+
stderrDone:=make(chanstruct{})
176+
done:=make(chanstruct{})
177+
178+
gofunc() {
179+
deferclose(stdinDone)
180+
// This is how we send commands to the agent.
181+
br:=bufio.NewScanner(inv.Stdin)
182+
// Split on bytes, otherwise you have to send a newline to flush the buffer.
183+
br.Split(bufio.ScanBytes)
184+
je:=json.NewEncoder(conn)
185+
forbr.Scan() {
186+
iferr:=je.Encode(map[string]string{
187+
"data":br.Text(),
188+
});err!=nil {
189+
return
190+
}
191+
}
192+
}()
193+
gofunc() {
194+
deferclose(stdoutDone)
195+
_,_=io.Copy(inv.Stdout,conn)
196+
}()
197+
gofunc() {
198+
deferclose(stderrDone)
199+
_,_=io.Copy(inv.Stderr,conn)
200+
}()
201+
gofunc() {
202+
deferclose(done)
203+
<-inv.Context().Done()
204+
_=conn.Close()
205+
}()
206+
207+
<-done
208+
209+
returnnil
210+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp