feat: Allow workspace resources to attach multiple agents (#942)

This enables a "kubernetes_pod" to attach multiple agents that
could be for multiple services. Each agent is required to have
a unique name, so SSH syntax is:

`coder ssh <workspace>.<agent>`

A resource can have zero agents too, they aren't required.
This commit is contained in:
Kyle Carberry
2022-04-11 16:06:15 -05:00
committed by GitHub
parent 2835bb45e5
commit 19b4323512
47 changed files with 1550 additions and 1040 deletions
+24 -28
View File
@@ -7,6 +7,7 @@ import (
"os"
"time"
"github.com/google/uuid"
"github.com/mattn/go-isatty"
"github.com/pion/webrtc/v3"
"github.com/spf13/cobra"
@@ -25,7 +26,7 @@ func ssh() *cobra.Command {
stdio bool
)
cmd := &cobra.Command{
Use: "ssh <workspace> [resource]",
Use: "ssh <workspace> [agent]",
RunE: func(cmd *cobra.Command, args []string) error {
client, err := createClient(cmd)
if err != nil {
@@ -57,50 +58,45 @@ func ssh() *cobra.Command {
return err
}
resourceByAddress := make(map[string]codersdk.WorkspaceResource)
agents := make([]codersdk.WorkspaceAgent, 0)
for _, resource := range resources {
if resource.Agent == nil {
continue
}
resourceByAddress[resource.Address] = resource
agents = append(agents, resource.Agents...)
}
var resourceAddress string
if len(agents) == 0 {
return xerrors.New("workspace has no agents")
}
var agent codersdk.WorkspaceAgent
if len(args) >= 2 {
resourceAddress = args[1]
} else {
// No resource name was provided!
if len(resourceByAddress) > 1 {
// List available resources to connect into?
return xerrors.Errorf("multiple agents")
}
for _, resource := range resourceByAddress {
resourceAddress = resource.Address
for _, otherAgent := range agents {
if otherAgent.Name != args[1] {
continue
}
agent = otherAgent
break
}
}
resource, exists := resourceByAddress[resourceAddress]
if !exists {
resourceKeys := make([]string, 0)
for resourceKey := range resourceByAddress {
resourceKeys = append(resourceKeys, resourceKey)
if agent.ID == uuid.Nil {
return xerrors.Errorf("agent not found by name %q", args[1])
}
return xerrors.Errorf("no sshable agent with address %q: %+v", resourceAddress, resourceKeys)
}
if agent.ID == uuid.Nil {
if len(agents) > 1 {
return xerrors.New("you must specify the name of an agent")
}
agent = agents[0]
}
// OpenSSH passes stderr directly to the calling TTY.
// This is required in "stdio" mode so a connecting indicator can be displayed.
err = cliui.Agent(cmd.Context(), cmd.ErrOrStderr(), cliui.AgentOptions{
WorkspaceName: workspace.Name,
Fetch: func(ctx context.Context) (codersdk.WorkspaceResource, error) {
return client.WorkspaceResource(ctx, resource.ID)
Fetch: func(ctx context.Context) (codersdk.WorkspaceAgent, error) {
return client.WorkspaceAgent(ctx, agent.ID)
},
})
if err != nil {
return xerrors.Errorf("await agent: %w", err)
}
conn, err := client.DialWorkspaceAgent(cmd.Context(), resource.ID, []webrtc.ICEServer{{
conn, err := client.DialWorkspaceAgent(cmd.Context(), agent.ID, []webrtc.ICEServer{{
URLs: []string{"stun:stun.l.google.com:19302"},
}}, nil)
if err != nil {