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

Commit9780d02

Browse files
feat(cli): add dynamic completions for ssh command (#20171)
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-agentworkspaces- Only shows running workspaces (matches immediate availability)- Alphabetically sorted completions for better UXTests cover single-agent, multi-agent, and network error scenarios.Amp-Thread-ID:https://ampcode.com/threads/T-d137d343-53f3-4ece-be5a-584249bbd9e8<!--If you have used AI to produce some or all of this PR, please ensure youhave read our [AI Contributionguidelines](https://coder.com/docs/about/contributing/AI_CONTRIBUTING)before submitting.-->closes#20158Demo:https://github.com/user-attachments/assets/e1000463-ded6-4bc9-b013-61780453f019---------Co-authored-by: Ethan Dickson <ethan@coder.com>
1 parentb890930 commit9780d02

File tree

2 files changed

+141
-0
lines changed

2 files changed

+141
-0
lines changed

‎cli/ssh.go‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,51 @@ 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{}
116+
}
117+
118+
res,err:=client.Workspaces(inv.Context(), codersdk.WorkspaceFilter{
119+
Owner:codersdk.Me,
120+
})
121+
iferr!=nil {
122+
return []string{}
123+
}
124+
125+
varmu sync.Mutex
126+
varcompletions []string
127+
varwg sync.WaitGroup
128+
for_,ws:=rangeres.Workspaces {
129+
wg.Add(1)
130+
gofunc() {
131+
deferwg.Done()
132+
resources,err:=client.TemplateVersionResources(inv.Context(),ws.LatestBuild.TemplateVersionID)
133+
iferr!=nil {
134+
return
135+
}
136+
varagents []codersdk.WorkspaceAgent
137+
for_,resource:=rangeresources {
138+
agents=append(agents,resource.Agents...)
139+
}
140+
141+
mu.Lock()
142+
defermu.Unlock()
143+
iflen(agents)==1 {
144+
completions=append(completions,ws.Name)
145+
}else {
146+
for_,agent:=rangeagents {
147+
completions=append(completions,fmt.Sprintf("%s.%s",ws.Name,agent.Name))
148+
}
149+
}
150+
}()
151+
}
152+
wg.Wait()
153+
154+
slices.Sort(completions)
155+
returncompletions
156+
},
112157
Handler:func(inv*serpent.Invocation) (retErrerror) {
113158
client,err:=r.InitClient(inv)
114159
iferr!=nil {

‎cli/ssh_test.go‎

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2447,3 +2447,99 @@ 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+
require.NoError(t,err)
2472+
2473+
// For single-agent workspaces, the only completion should be the
2474+
// bare workspace name.
2475+
output:=stdout.String()
2476+
t.Logf("Completion output: %q",output)
2477+
require.Contains(t,output,workspace.Name)
2478+
})
2479+
2480+
t.Run("MultiAgent",func(t*testing.T) {
2481+
t.Parallel()
2482+
2483+
client,store:=coderdtest.NewWithDatabase(t,nil)
2484+
first:=coderdtest.CreateFirstUser(t,client)
2485+
userClient,user:=coderdtest.CreateAnotherUserMutators(t,client,first.OrganizationID,nil,func(r*codersdk.CreateUserRequestWithOrgs) {
2486+
r.Username="multiuser"
2487+
})
2488+
2489+
r:=dbfake.WorkspaceBuild(t,store, database.WorkspaceTable{
2490+
Name:"multiworkspace",
2491+
OrganizationID:first.OrganizationID,
2492+
OwnerID:user.ID,
2493+
}).WithAgent(func(agents []*proto.Agent) []*proto.Agent {
2494+
return []*proto.Agent{
2495+
{
2496+
Name:"agent1",
2497+
Auth:&proto.Agent_Token{},
2498+
},
2499+
{
2500+
Name:"agent2",
2501+
Auth:&proto.Agent_Token{},
2502+
},
2503+
}
2504+
}).Do()
2505+
2506+
varstdout bytes.Buffer
2507+
inv,root:=clitest.New(t,"ssh","")
2508+
inv.Stdout=&stdout
2509+
inv.Environ.Set("COMPLETION_MODE","1")
2510+
clitest.SetupConfig(t,userClient,root)
2511+
2512+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitMedium)
2513+
defercancel()
2514+
2515+
err:=inv.WithContext(ctx).Run()
2516+
require.NoError(t,err)
2517+
2518+
// For multi-agent workspaces, completions should include the
2519+
// workspace.agent format but NOT the bare workspace name.
2520+
output:=stdout.String()
2521+
t.Logf("Completion output: %q",output)
2522+
lines:=strings.Split(strings.TrimSpace(output),"\n")
2523+
require.NotContains(t,lines,r.Workspace.Name)
2524+
require.Contains(t,output,r.Workspace.Name+".agent1")
2525+
require.Contains(t,output,r.Workspace.Name+".agent2")
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+
require.NoError(t,err)
2541+
2542+
output:=stdout.String()
2543+
require.Empty(t,output)
2544+
})
2545+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp