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

Commit6e15bbb

Browse files
cli: add dynamic completions for ssh command
Adds CompletionHandler to the ssh command that dynamically suggestsworkspace and agent targets based on the user's running workspaces.Features:- Suggests workspace name for single-agent workspaces- Suggests agent.workspace format for all agents in multi-agent workspaces- Only shows running workspaces (matches immediate availability)- Alphabetically sorted completions for better UX- Graceful error handling (returns error strings for debugging)Tests cover single-agent, multi-agent, and network error scenarios.Amp-Thread-ID:https://ampcode.com/threads/T-d137d343-53f3-4ece-be5a-584249bbd9e8Co-authored-by: Amp <amp@ampcode.com>
1 parent2b44855 commit6e15bbb

File tree

2 files changed

+133
-0
lines changed

2 files changed

+133
-0
lines changed

‎cli/ssh.go‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,42 @@ func (r *RootCmd) ssh() *serpent.Command {
109109
}
110110
},
111111
),
112+
CompletionHandler:func(inv*serpent.Invocation) []string {
113+
client,err:=r.InitClient(inv)
114+
iferr!=nil {
115+
return []string{"# Error: Unable to initialize client - "+err.Error()}
116+
}
117+
118+
res,err:=client.Workspaces(inv.Context(), codersdk.WorkspaceFilter{
119+
Owner:"me",
120+
})
121+
iferr!=nil {
122+
return []string{"# Error: Unable to fetch workspaces - "+err.Error()}
123+
}
124+
125+
varcompletions []string
126+
for_,ws:=rangeres.Workspaces {
127+
ifws.LatestBuild.Status!=codersdk.WorkspaceStatusRunning {
128+
continue
129+
}
130+
131+
varagents []codersdk.WorkspaceAgent
132+
for_,resource:=rangews.LatestBuild.Resources {
133+
agents=append(agents,resource.Agents...)
134+
}
135+
136+
iflen(agents)==1 {
137+
completions=append(completions,ws.Name)
138+
}
139+
140+
for_,agent:=rangeagents {
141+
completions=append(completions,fmt.Sprintf("%s.%s",agent.Name,ws.Name))
142+
}
143+
}
144+
145+
slices.Sort(completions)
146+
returncompletions
147+
},
112148
Handler:func(inv*serpent.Invocation) (retErrerror) {
113149
client,err:=r.InitClient(inv)
114150
iferr!=nil {

‎cli/ssh_test.go‎

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2447,3 +2447,100 @@ func tempDirUnixSocket(t *testing.T) string {
24472447

24482448
returnt.TempDir()
24492449
}
2450+
2451+
funcTestSSH_Completion(t*testing.T) {
2452+
t.Parallel()
2453+
2454+
t.Run("SingleAgent",func(t*testing.T) {
2455+
t.Parallel()
2456+
2457+
client,workspace,agentToken:=setupWorkspaceForAgent(t)
2458+
_=agenttest.New(t,client.URL,agentToken)
2459+
coderdtest.AwaitWorkspaceAgents(t,client,workspace.ID)
2460+
2461+
varstdout bytes.Buffer
2462+
inv,root:=clitest.New(t,"ssh","")
2463+
inv.Stdout=&stdout
2464+
inv.Environ.Set("COMPLETION_MODE","1")
2465+
clitest.SetupConfig(t,client,root)
2466+
2467+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitMedium)
2468+
defercancel()
2469+
2470+
err:=inv.WithContext(ctx).Run()
2471+
// Completion handlers may fail gracefully, so we don't assert error
2472+
_=err
2473+
2474+
// For single-agent workspaces, the workspace name should be suggested
2475+
// as an alias
2476+
output:=stdout.String()
2477+
t.Logf("Completion output: %q",output)
2478+
require.Contains(t,output,workspace.Name)
2479+
})
2480+
2481+
t.Run("MultiAgent",func(t*testing.T) {
2482+
t.Parallel()
2483+
2484+
client,store:=coderdtest.NewWithDatabase(t,nil)
2485+
first:=coderdtest.CreateFirstUser(t,client)
2486+
userClient,user:=coderdtest.CreateAnotherUserMutators(t,client,first.OrganizationID,nil,func(r*codersdk.CreateUserRequestWithOrgs) {
2487+
r.Username="multiuser"
2488+
})
2489+
2490+
// Create a workspace with multiple agents
2491+
r:=dbfake.WorkspaceBuild(t,store, database.WorkspaceTable{
2492+
Name:"multiworkspace",
2493+
OrganizationID:first.OrganizationID,
2494+
OwnerID:user.ID,
2495+
}).WithAgent(func(agents []*proto.Agent) []*proto.Agent {
2496+
return []*proto.Agent{
2497+
{
2498+
Name:"agent1",
2499+
Auth:&proto.Agent_Token{},
2500+
},
2501+
{
2502+
Name:"agent2",
2503+
Auth:&proto.Agent_Token{},
2504+
},
2505+
}
2506+
}).Do()
2507+
2508+
varstdout bytes.Buffer
2509+
inv,root:=clitest.New(t,"ssh","")
2510+
inv.Stdout=&stdout
2511+
inv.Environ.Set("COMPLETION_MODE","1")
2512+
clitest.SetupConfig(t,userClient,root)
2513+
2514+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitMedium)
2515+
defercancel()
2516+
2517+
err:=inv.WithContext(ctx).Run()
2518+
// Completion handlers may fail gracefully, so we don't assert error
2519+
_=err
2520+
2521+
// For multi-agent workspaces, completions should include agent.workspace format
2522+
output:=stdout.String()
2523+
t.Logf("Completion output: %q",output)
2524+
require.Contains(t,output,"agent1."+r.Workspace.Name)
2525+
require.Contains(t,output,"agent2."+r.Workspace.Name)
2526+
})
2527+
2528+
t.Run("NetworkError",func(t*testing.T) {
2529+
t.Parallel()
2530+
2531+
varstdout bytes.Buffer
2532+
inv,_:=clitest.New(t,"ssh","")
2533+
inv.Stdout=&stdout
2534+
inv.Environ.Set("COMPLETION_MODE","1")
2535+
2536+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitShort)
2537+
defercancel()
2538+
2539+
err:=inv.WithContext(ctx).Run()
2540+
_=err
2541+
2542+
output:=stdout.String()
2543+
t.Logf("Completion output with error: %q",output)
2544+
require.Contains(t,output,"# Error:")
2545+
})
2546+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp