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

feat(cli): add dynamic completions for ssh command#20171

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Draft
matifali wants to merge3 commits intomain
base:main
Choose a base branch
Loading
fromcli/ssh-dynamic-completions
Draft
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletionscli/ssh.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -109,6 +109,42 @@ func (r *RootCmd) ssh() *serpent.Command {
}
},
),
CompletionHandler: func(inv *serpent.Invocation) []string {
client, err := r.InitClient(inv)
if err != nil {
return []string{"# Error: Unable to initialize client - " + err.Error()}
}

res, err := client.Workspaces(inv.Context(), codersdk.WorkspaceFilter{
Owner: "me",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Could beOwner: codersdk.Me I think

})
if err != nil {
return []string{"# Error: Unable to fetch workspaces - " + err.Error()}
}

var completions []string
for _, ws := range res.Workspaces {
if ws.LatestBuild.Status != codersdk.WorkspaceStatusRunning {
continue
}
Comment on lines +127 to +129
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Can we show workspaces that are not running? Could be convenient, otherwise you have to start the workspace you want first, and thenssh (see next comment too).


var agents []codersdk.WorkspaceAgent
for _, resource := range ws.LatestBuild.Resources {
agents = append(agents, resource.Agents...)
}
Comment on lines +131 to +134
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

If we decide to allow workspaces that are not running, since those will not have agents in the response we would have to add a call to template version resources. We could take inspiration from the function we used to have inconfigssh.go:

coder/cli/configssh.go

Lines 147 to 195 in0b2ba96

funcsshFetchWorkspaceConfigs(ctx context.Context,client*codersdk.Client) ([]sshWorkspaceConfig,error) {
res,err:=client.Workspaces(ctx, codersdk.WorkspaceFilter{
Owner:codersdk.Me,
})
iferr!=nil {
returnnil,err
}
varerrGroup errgroup.Group
workspaceConfigs:=make([]sshWorkspaceConfig,len(res.Workspaces))
fori,workspace:=rangeres.Workspaces {
i:=i
workspace:=workspace
errGroup.Go(func()error {
resources,err:=client.TemplateVersionResources(ctx,workspace.LatestBuild.TemplateVersionID)
iferr!=nil {
returnerr
}
wc:=sshWorkspaceConfig{Name:workspace.Name}
varagents []codersdk.WorkspaceAgent
for_,resource:=rangeresources {
ifresource.Transition!=codersdk.WorkspaceTransitionStart {
continue
}
agents=append(agents,resource.Agents...)
}
// handle both WORKSPACE and WORKSPACE.AGENT syntax
iflen(agents)==1 {
wc.Hosts=append(wc.Hosts,workspace.Name)
}
for_,agent:=rangeagents {
hostname:=workspace.Name+"."+agent.Name
wc.Hosts=append(wc.Hosts,hostname)
}
workspaceConfigs[i]=wc
returnnil
})
}
err=errGroup.Wait()
iferr!=nil {
returnnil,err
}
returnworkspaceConfigs,nil
}


if len(agents) == 1 {
completions = append(completions, ws.Name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

What do you think about always adding this completion? Without the agentssh will use the first one and it could be convenient if a workspace has multiple agents that you can use the shorter syntax.

}

for _, agent := range agents {
completions = append(completions, fmt.Sprintf("%s.%s", agent.Name, ws.Name))
}
}

slices.Sort(completions)
return completions
},
Handler: func(inv *serpent.Invocation) (retErr error) {
client, err := r.InitClient(inv)
if err != nil {
Expand Down
100 changes: 100 additions & 0 deletionscli/ssh_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -2447,3 +2447,103 @@ func tempDirUnixSocket(t *testing.T) string {

return t.TempDir()
}

func TestSSH_Completion(t *testing.T) {
t.Parallel()

t.Run("SingleAgent", func(t *testing.T) {
t.Parallel()

client, workspace, agentToken := setupWorkspaceForAgent(t)
_ = agenttest.New(t, client.URL, agentToken)
coderdtest.AwaitWorkspaceAgents(t, client, workspace.ID)

var stdout bytes.Buffer
inv, root := clitest.New(t, "ssh", "")
inv.Stdout = &stdout
inv.Environ.Set("COMPLETION_MODE", "1")
clitest.SetupConfig(t, client, root)

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium)
defer cancel()

err := inv.WithContext(ctx).Run()
// Completion handlers may fail gracefully, so we don't assert error
_ = err
Comment on lines +2470 to +2472
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I am not quite following, why can we not require.NoError(err)? Looks like the handler never errors, but if it did that would be a problem we should catch right?


// For single-agent workspaces, the workspace name should be suggested
// as an alias
output := stdout.String()
t.Logf("Completion output: %q", output)
require.Contains(t, output, workspace.Name)
})

t.Run("MultiAgent", func(t *testing.T) {
t.Parallel()

client, store := coderdtest.NewWithDatabase(t, nil)
first := coderdtest.CreateFirstUser(t, client)
userClient, user := coderdtest.CreateAnotherUserMutators(t, client, first.OrganizationID, nil, func(r *codersdk.CreateUserRequestWithOrgs) {
r.Username = "multiuser"
})

// Create a workspace with multiple agents
r := dbfake.WorkspaceBuild(t, store, database.WorkspaceTable{
Name: "multiworkspace",
OrganizationID: first.OrganizationID,
OwnerID: user.ID,
}).WithAgent(func(agents []*proto.Agent) []*proto.Agent {
return []*proto.Agent{
{
Name: "agent1",
Auth: &proto.Agent_Token{},
},
{
Name: "agent2",
Auth: &proto.Agent_Token{},
},
}
}).Do()

var stdout bytes.Buffer
inv, root := clitest.New(t, "ssh", "")
inv.Stdout = &stdout
inv.Environ.Set("COMPLETION_MODE", "1")
clitest.SetupConfig(t, userClient, root)

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium)
defer cancel()

err := inv.WithContext(ctx).Run()
// Completion handlers may fail gracefully, so we don't assert error
_ = err
Comment on lines +2517 to +2519
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Same here


// For multi-agent workspaces, completions should include agent.workspace format
// but NOT the bare workspace name
output := stdout.String()
t.Logf("Completion output: %q", output)
lines := strings.Split(strings.TrimSpace(output), "\n")
require.NotContains(t, lines, r.Workspace.Name)
require.Contains(t, output, "agent1."+r.Workspace.Name)
require.Contains(t, output, "agent2."+r.Workspace.Name)
})

t.Run("NetworkError", func(t *testing.T) {
t.Parallel()

var stdout bytes.Buffer
inv, _ := clitest.New(t, "ssh", "")
inv.Stdout = &stdout
inv.Environ.Set("COMPLETION_MODE", "1")

ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitShort)
defer cancel()

err := inv.WithContext(ctx).Run()
_ = err
Comment on lines +2542 to +2543
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

And here


output := stdout.String()
t.Logf("Completion output with error: %q", output)
require.Contains(t, output, "# Error:")
})
}
Loading

[8]ページ先頭

©2009-2025 Movatter.jp