Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 19b4323

Browse files
authored
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.
1 parent 2835bb4 commit 19b4323

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1536
-1026
lines changed

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"emeraldwalk.runonsave": {
6363
"commands": [
6464
{
65-
"match": "database/query.sql",
65+
"match": "database/queries/*.sql",
6666
"cmd": "make gen"
6767
}
6868
]

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@ fmt/sql: $(wildcard coderd/database/queries/*.sql)
4242

4343
sed -i 's/@ /@/g' ./coderd/database/queries/*.sql
4444

45-
fmt: fmt/prettier fmt/sql
45+
fmt/terraform: $(wildcard *.tf)
46+
terraform fmt -recursive
47+
48+
fmt: fmt/prettier fmt/sql fmt/terraform
4649
.PHONY: fmt
4750

4851
gen: coderd/database/generate peerbroker/proto provisionersdk/proto provisionerd/proto

cli/cliui/agent.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515

1616
type AgentOptions struct {
1717
WorkspaceName string
18-
Fetch func(context.Context) (codersdk.WorkspaceResource, error)
18+
Fetch func(context.Context) (codersdk.WorkspaceAgent, error)
1919
FetchInterval time.Duration
2020
WarnInterval time.Duration
2121
}
@@ -29,20 +29,20 @@ func Agent(ctx context.Context, writer io.Writer, opts AgentOptions) error {
2929
opts.WarnInterval = 30 * time.Second
3030
}
3131
var resourceMutex sync.Mutex
32-
resource, err := opts.Fetch(ctx)
32+
agent, err := opts.Fetch(ctx)
3333
if err != nil {
3434
return xerrors.Errorf("fetch: %w", err)
3535
}
36-
if resource.Agent.Status == codersdk.WorkspaceAgentConnected {
36+
if agent.Status == codersdk.WorkspaceAgentConnected {
3737
return nil
3838
}
39-
if resource.Agent.Status == codersdk.WorkspaceAgentDisconnected {
39+
if agent.Status == codersdk.WorkspaceAgentDisconnected {
4040
opts.WarnInterval = 0
4141
}
4242
spin := spinner.New(spinner.CharSets[78], 100*time.Millisecond, spinner.WithColor("fgHiGreen"))
4343
spin.Writer = writer
4444
spin.ForceOutput = true
45-
spin.Suffix = " Waiting for connection from " + Styles.Field.Render(resource.Type+"."+resource.Name) + "..."
45+
spin.Suffix = " Waiting for connection from " + Styles.Field.Render(agent.Name) + "..."
4646
spin.Start()
4747
defer spin.Stop()
4848

@@ -59,7 +59,7 @@ func Agent(ctx context.Context, writer io.Writer, opts AgentOptions) error {
5959
resourceMutex.Lock()
6060
defer resourceMutex.Unlock()
6161
message := "Don't panic, your workspace is booting up!"
62-
if resource.Agent.Status == codersdk.WorkspaceAgentDisconnected {
62+
if agent.Status == codersdk.WorkspaceAgentDisconnected {
6363
message = "The workspace agent lost connection! Wait for it to reconnect or run: " + Styles.Code.Render("coder workspaces rebuild "+opts.WorkspaceName)
6464
}
6565
// This saves the cursor position, then defers clearing from the cursor
@@ -74,11 +74,11 @@ func Agent(ctx context.Context, writer io.Writer, opts AgentOptions) error {
7474
case <-ticker.C:
7575
}
7676
resourceMutex.Lock()
77-
resource, err = opts.Fetch(ctx)
77+
agent, err = opts.Fetch(ctx)
7878
if err != nil {
7979
return xerrors.Errorf("fetch: %w", err)
8080
}
81-
if resource.Agent.Status != codersdk.WorkspaceAgentConnected {
81+
if agent.Status != codersdk.WorkspaceAgentConnected {
8282
resourceMutex.Unlock()
8383
continue
8484
}

cli/cliui/agent_test.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,14 @@ func TestAgent(t *testing.T) {
2222
RunE: func(cmd *cobra.Command, args []string) error {
2323
err := cliui.Agent(cmd.Context(), cmd.OutOrStdout(), cliui.AgentOptions{
2424
WorkspaceName: "example",
25-
Fetch: func(ctx context.Context) (codersdk.WorkspaceResource, error) {
26-
resource := codersdk.WorkspaceResource{
27-
Agent: &codersdk.WorkspaceAgent{
28-
Status: codersdk.WorkspaceAgentDisconnected,
29-
},
25+
Fetch: func(ctx context.Context) (codersdk.WorkspaceAgent, error) {
26+
agent := codersdk.WorkspaceAgent{
27+
Status: codersdk.WorkspaceAgentDisconnected,
3028
}
3129
if disconnected.Load() {
32-
resource.Agent.Status = codersdk.WorkspaceAgentConnected
30+
agent.Status = codersdk.WorkspaceAgentConnected
3331
}
34-
return resource, nil
32+
return agent, nil
3533
},
3634
FetchInterval: time.Millisecond,
3735
WarnInterval: 10 * time.Millisecond,

cli/configssh.go

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
"github.com/coder/coder/cli/cliflag"
1717
"github.com/coder/coder/cli/cliui"
18+
"github.com/coder/coder/coderd/database"
1819
"github.com/coder/coder/codersdk"
1920
)
2021

@@ -70,29 +71,30 @@ func configSSH() *cobra.Command {
7071
for _, workspace := range workspaces {
7172
workspace := workspace
7273
errGroup.Go(func() error {
73-
resources, err := client.WorkspaceResourcesByBuild(cmd.Context(), workspace.LatestBuild.ID)
74+
resources, err := client.TemplateVersionResources(cmd.Context(), workspace.LatestBuild.TemplateVersionID)
7475
if err != nil {
7576
return err
7677
}
77-
resourcesWithAgents := make([]codersdk.WorkspaceResource, 0)
7878
for _, resource := range resources {
79-
if resource.Agent == nil {
79+
if resource.Transition != database.WorkspaceTransitionStart {
8080
continue
8181
}
82-
resourcesWithAgents = append(resourcesWithAgents, resource)
83-
}
84-
sshConfigContentMutex.Lock()
85-
defer sshConfigContentMutex.Unlock()
86-
if len(resourcesWithAgents) == 1 {
87-
sshConfigContent += strings.Join([]string{
88-
"Host coder." + workspace.Name,
89-
"\tHostName coder." + workspace.Name,
90-
fmt.Sprintf("\tProxyCommand %q ssh --stdio %s", binPath, workspace.Name),
91-
"\tConnectTimeout=0",
92-
"\tStrictHostKeyChecking=no",
93-
}, "\n") + "\n"
82+
for _, agent := range resource.Agents {
83+
sshConfigContentMutex.Lock()
84+
hostname := workspace.Name
85+
if len(resource.Agents) > 1 {
86+
hostname += "." + agent.Name
87+
}
88+
sshConfigContent += strings.Join([]string{
89+
"Host coder." + hostname,
90+
"\tHostName coder." + hostname,
91+
fmt.Sprintf("\tProxyCommand %q ssh --stdio %s", binPath, hostname),
92+
"\tConnectTimeout=0",
93+
"\tStrictHostKeyChecking=no",
94+
}, "\n") + "\n"
95+
sshConfigContentMutex.Unlock()
96+
}
9497
}
95-
9698
return nil
9799
})
98100
}

cli/gitssh_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,11 @@ func TestGitSSH(t *testing.T) {
4949
Resources: []*proto.Resource{{
5050
Name: "somename",
5151
Type: "someinstance",
52-
Agent: &proto.Agent{
52+
Agents: []*proto.Agent{{
5353
Auth: &proto.Agent_InstanceId{
5454
InstanceId: instanceID,
5555
},
56-
},
56+
}},
5757
}},
5858
},
5959
},
@@ -81,7 +81,7 @@ func TestGitSSH(t *testing.T) {
8181
coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID)
8282
resources, err := client.WorkspaceResourcesByBuild(ctx, workspace.LatestBuild.ID)
8383
require.NoError(t, err)
84-
dialer, err := client.DialWorkspaceAgent(ctx, resources[0].ID, nil, nil)
84+
dialer, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil, nil)
8585
require.NoError(t, err)
8686
defer dialer.Close()
8787
_, err = dialer.Ping()

cli/ssh.go

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"os"
88
"time"
99

10+
"github.com/google/uuid"
1011
"github.com/mattn/go-isatty"
1112
"github.com/pion/webrtc/v3"
1213
"github.com/spf13/cobra"
@@ -25,7 +26,7 @@ func ssh() *cobra.Command {
2526
stdio bool
2627
)
2728
cmd := &cobra.Command{
28-
Use: "ssh <workspace> [resource]",
29+
Use: "ssh <workspace> [agent]",
2930
RunE: func(cmd *cobra.Command, args []string) error {
3031
client, err := createClient(cmd)
3132
if err != nil {
@@ -57,50 +58,45 @@ func ssh() *cobra.Command {
5758
return err
5859
}
5960

60-
resourceByAddress := make(map[string]codersdk.WorkspaceResource)
61+
agents := make([]codersdk.WorkspaceAgent, 0)
6162
for _, resource := range resources {
62-
if resource.Agent == nil {
63-
continue
64-
}
65-
resourceByAddress[resource.Address] = resource
63+
agents = append(agents, resource.Agents...)
6664
}
67-
68-
var resourceAddress string
65+
if len(agents) == 0 {
66+
return xerrors.New("workspace has no agents")
67+
}
68+
var agent codersdk.WorkspaceAgent
6969
if len(args) >= 2 {
70-
resourceAddress = args[1]
71-
} else {
72-
// No resource name was provided!
73-
if len(resourceByAddress) > 1 {
74-
// List available resources to connect into?
75-
return xerrors.Errorf("multiple agents")
76-
}
77-
for _, resource := range resourceByAddress {
78-
resourceAddress = resource.Address
70+
for _, otherAgent := range agents {
71+
if otherAgent.Name != args[1] {
72+
continue
73+
}
74+
agent = otherAgent
7975
break
8076
}
77+
if agent.ID == uuid.Nil {
78+
return xerrors.Errorf("agent not found by name %q", args[1])
79+
}
8180
}
82-
83-
resource, exists := resourceByAddress[resourceAddress]
84-
if !exists {
85-
resourceKeys := make([]string, 0)
86-
for resourceKey := range resourceByAddress {
87-
resourceKeys = append(resourceKeys, resourceKey)
81+
if agent.ID == uuid.Nil {
82+
if len(agents) > 1 {
83+
return xerrors.New("you must specify the name of an agent")
8884
}
89-
return xerrors.Errorf("no sshable agent with address %q: %+v", resourceAddress, resourceKeys)
85+
agent = agents[0]
9086
}
9187
// OpenSSH passes stderr directly to the calling TTY.
9288
// This is required in "stdio" mode so a connecting indicator can be displayed.
9389
err = cliui.Agent(cmd.Context(), cmd.ErrOrStderr(), cliui.AgentOptions{
9490
WorkspaceName: workspace.Name,
95-
Fetch: func(ctx context.Context) (codersdk.WorkspaceResource, error) {
96-
return client.WorkspaceResource(ctx, resource.ID)
91+
Fetch: func(ctx context.Context) (codersdk.WorkspaceAgent, error) {
92+
return client.WorkspaceAgent(ctx, agent.ID)
9793
},
9894
})
9995
if err != nil {
10096
return xerrors.Errorf("await agent: %w", err)
10197
}
10298

103-
conn, err := client.DialWorkspaceAgent(cmd.Context(), resource.ID, []webrtc.ICEServer{{
99+
conn, err := client.DialWorkspaceAgent(cmd.Context(), agent.ID, []webrtc.ICEServer{{
104100
URLs: []string{"stun:stun.l.google.com:19302"},
105101
}}, nil)
106102
if err != nil {

cli/ssh_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,12 @@ func TestSSH(t *testing.T) {
4040
Resources: []*proto.Resource{{
4141
Name: "dev",
4242
Type: "google_compute_instance",
43-
Agent: &proto.Agent{
43+
Agents: []*proto.Agent{{
4444
Id: uuid.NewString(),
4545
Auth: &proto.Agent_Token{
4646
Token: agentToken,
4747
},
48-
},
48+
}},
4949
}},
5050
},
5151
},
@@ -98,12 +98,12 @@ func TestSSH(t *testing.T) {
9898
Resources: []*proto.Resource{{
9999
Name: "dev",
100100
Type: "google_compute_instance",
101-
Agent: &proto.Agent{
101+
Agents: []*proto.Agent{{
102102
Id: uuid.NewString(),
103103
Auth: &proto.Agent_Token{
104104
Token: agentToken,
105105
},
106-
},
106+
}},
107107
}},
108108
},
109109
},

cli/templates.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func displayTemplateVersionInfo(cmd *cobra.Command, resources []codersdk.Workspa
7474
} else {
7575
_, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+cliui.Styles.Keyword.Render("+ start")+cliui.Styles.Placeholder.Render(" (deletes on stop)"))
7676
}
77-
if resource.Agent != nil {
77+
if len(resource.Agents) > 0 {
7878
_, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+cliui.Styles.Fuschia.Render("▲ allows ssh"))
7979
}
8080
_, _ = fmt.Fprintln(cmd.OutOrStdout())

cli/workspaceagent_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ func TestWorkspaceAgent(t *testing.T) {
3232
Resources: []*proto.Resource{{
3333
Name: "somename",
3434
Type: "someinstance",
35-
Agent: &proto.Agent{
35+
Agents: []*proto.Agent{{
3636
Auth: &proto.Agent_InstanceId{
3737
InstanceId: instanceID,
3838
},
39-
},
39+
}},
4040
}},
4141
},
4242
},
@@ -61,7 +61,7 @@ func TestWorkspaceAgent(t *testing.T) {
6161
coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID)
6262
resources, err := client.WorkspaceResourcesByBuild(ctx, workspace.LatestBuild.ID)
6363
require.NoError(t, err)
64-
dialer, err := client.DialWorkspaceAgent(ctx, resources[0].ID, nil, nil)
64+
dialer, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil, nil)
6565
require.NoError(t, err)
6666
defer dialer.Close()
6767
_, err = dialer.Ping()
@@ -86,11 +86,11 @@ func TestWorkspaceAgent(t *testing.T) {
8686
Resources: []*proto.Resource{{
8787
Name: "somename",
8888
Type: "someinstance",
89-
Agent: &proto.Agent{
89+
Agents: []*proto.Agent{{
9090
Auth: &proto.Agent_InstanceId{
9191
InstanceId: instanceID,
9292
},
93-
},
93+
}},
9494
}},
9595
},
9696
},
@@ -115,7 +115,7 @@ func TestWorkspaceAgent(t *testing.T) {
115115
coderdtest.AwaitWorkspaceAgents(t, client, workspace.LatestBuild.ID)
116116
resources, err := client.WorkspaceResourcesByBuild(ctx, workspace.LatestBuild.ID)
117117
require.NoError(t, err)
118-
dialer, err := client.DialWorkspaceAgent(ctx, resources[0].ID, nil, nil)
118+
dialer, err := client.DialWorkspaceAgent(ctx, resources[0].Agents[0].ID, nil, nil)
119119
require.NoError(t, err)
120120
defer dialer.Close()
121121
_, err = dialer.Ping()

cli/workspaceshow.go

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,13 @@
11
package cli
22

33
import (
4-
"fmt"
5-
64
"github.com/spf13/cobra"
7-
8-
"github.com/coder/coder/codersdk"
95
)
106

117
func workspaceShow() *cobra.Command {
128
return &cobra.Command{
139
Use: "show",
1410
RunE: func(cmd *cobra.Command, args []string) error {
15-
client, err := createClient(cmd)
16-
if err != nil {
17-
return err
18-
}
19-
workspace, err := client.WorkspaceByName(cmd.Context(), codersdk.Me, args[0])
20-
if err != nil {
21-
return err
22-
}
23-
resources, err := client.WorkspaceResourcesByBuild(cmd.Context(), workspace.LatestBuild.ID)
24-
if err != nil {
25-
return err
26-
}
27-
for _, resource := range resources {
28-
if resource.Agent == nil {
29-
continue
30-
}
31-
32-
_, _ = fmt.Printf("Agent: %+v\n", resource.Agent)
33-
}
3411
return nil
3512
},
3613
}

0 commit comments

Comments
 (0)