Skip to content

Commit 2d8154e

Browse files
committed
feat(cli): display open ports in coder show
1 parent 31b1ff7 commit 2d8154e

File tree

2 files changed

+100
-26
lines changed

2 files changed

+100
-26
lines changed

cli/cliui/resources.go

Lines changed: 61 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import (
55
"io"
66
"sort"
77
"strconv"
8+
"strings"
89

10+
"github.com/google/uuid"
911
"github.com/jedib0t/go-pretty/v6/table"
1012
"golang.org/x/mod/semver"
1113

@@ -14,12 +16,18 @@ import (
1416
"github.com/coder/pretty"
1517
)
1618

19+
var (
20+
pipeMid = "├"
21+
pipeEnd = "└"
22+
)
23+
1724
type WorkspaceResourcesOptions struct {
1825
WorkspaceName string
1926
HideAgentState bool
2027
HideAccess bool
2128
Title string
2229
ServerVersion string
30+
ListeningPorts map[uuid.UUID]codersdk.WorkspaceAgentListeningPortsResponse
2331
}
2432

2533
// WorkspaceResources displays the connection status and tree-view of provided resources.
@@ -86,39 +94,61 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource
8694
})
8795
// Display all agents associated with the resource.
8896
for index, agent := range resource.Agents {
89-
pipe := "├"
90-
if index == len(resource.Agents)-1 {
91-
pipe = "└"
92-
}
93-
row := table.Row{
94-
// These tree from a resource!
95-
fmt.Sprintf("%s─ %s (%s, %s)", pipe, agent.Name, agent.OperatingSystem, agent.Architecture),
96-
}
97-
if !options.HideAgentState {
98-
var agentStatus, agentHealth, agentVersion string
99-
if !options.HideAgentState {
100-
agentStatus = renderAgentStatus(agent)
101-
agentHealth = renderAgentHealth(agent)
102-
agentVersion = renderAgentVersion(agent.Version, options.ServerVersion)
103-
}
104-
row = append(row, agentStatus, agentHealth, agentVersion)
105-
}
106-
if !options.HideAccess {
107-
sshCommand := "coder ssh " + options.WorkspaceName
108-
if totalAgents > 1 {
109-
sshCommand += "." + agent.Name
97+
tableWriter.AppendRow(renderAgentRow(agent, index, totalAgents, options))
98+
if options.ListeningPorts != nil {
99+
if lp, ok := options.ListeningPorts[agent.ID]; ok {
100+
tableWriter.AppendRow(table.Row{
101+
fmt.Sprintf(" %s─ %s", renderPipe(index, totalAgents), "Open Ports"),
102+
})
103+
for _, port := range lp.Ports {
104+
tableWriter.AppendRow(renderPortRow(port, index, totalAgents))
105+
}
110106
}
111-
sshCommand = pretty.Sprint(DefaultStyles.Code, sshCommand)
112-
row = append(row, sshCommand)
113107
}
114-
tableWriter.AppendRow(row)
115108
}
116109
tableWriter.AppendSeparator()
117110
}
118111
_, err := fmt.Fprintln(writer, tableWriter.Render())
119112
return err
120113
}
121114

115+
func renderAgentRow(agent codersdk.WorkspaceAgent, index, totalAgents int, options WorkspaceResourcesOptions) table.Row {
116+
row := table.Row{
117+
// These tree from a resource!
118+
fmt.Sprintf("%s─ %s (%s, %s)", renderPipe(index, totalAgents), agent.Name, agent.OperatingSystem, agent.Architecture),
119+
}
120+
if !options.HideAgentState {
121+
var agentStatus, agentHealth, agentVersion string
122+
if !options.HideAgentState {
123+
agentStatus = renderAgentStatus(agent)
124+
agentHealth = renderAgentHealth(agent)
125+
agentVersion = renderAgentVersion(agent.Version, options.ServerVersion)
126+
}
127+
row = append(row, agentStatus, agentHealth, agentVersion)
128+
}
129+
if !options.HideAccess {
130+
sshCommand := "coder ssh " + options.WorkspaceName
131+
if totalAgents > 1 {
132+
sshCommand += "." + agent.Name
133+
}
134+
sshCommand = pretty.Sprint(DefaultStyles.Code, sshCommand)
135+
row = append(row, sshCommand)
136+
}
137+
return row
138+
}
139+
140+
func renderPortRow(port codersdk.WorkspaceAgentListeningPort, index, totalPorts int) table.Row {
141+
var sb strings.Builder
142+
_, _ = sb.WriteString(" ")
143+
_, _ = sb.WriteString(renderPipe(index, totalPorts))
144+
_, _ = sb.WriteString("─ ")
145+
_, _ = sb.WriteString(pretty.Sprintf(DefaultStyles.Code, "%5d/%s", port.Port, port.Network))
146+
if port.ProcessName != "" {
147+
_, _ = sb.WriteString(pretty.Sprintf(DefaultStyles.Keyword, " [%s]", port.ProcessName))
148+
}
149+
return table.Row{sb.String()}
150+
}
151+
122152
func renderAgentStatus(agent codersdk.WorkspaceAgent) string {
123153
switch agent.Status {
124154
case codersdk.WorkspaceAgentConnecting:
@@ -163,3 +193,10 @@ func renderAgentVersion(agentVersion, serverVersion string) string {
163193
}
164194
return pretty.Sprint(DefaultStyles.Keyword, agentVersion)
165195
}
196+
197+
func renderPipe(idx, total int) string {
198+
if idx == total-1 {
199+
return pipeEnd
200+
}
201+
return pipeMid
202+
}

cli/show.go

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
package cli
22

33
import (
4+
"sort"
5+
"sync"
6+
47
"golang.org/x/xerrors"
58

9+
"github.com/google/uuid"
10+
611
"github.com/coder/coder/v2/cli/cliui"
712
"github.com/coder/coder/v2/codersdk"
813
"github.com/coder/serpent"
@@ -26,10 +31,42 @@ func (r *RootCmd) show() *serpent.Command {
2631
if err != nil {
2732
return xerrors.Errorf("get workspace: %w", err)
2833
}
29-
return cliui.WorkspaceResources(inv.Stdout, workspace.LatestBuild.Resources, cliui.WorkspaceResourcesOptions{
34+
35+
options := cliui.WorkspaceResourcesOptions{
3036
WorkspaceName: workspace.Name,
3137
ServerVersion: buildInfo.Version,
32-
})
38+
}
39+
if workspace.LatestBuild.Status == codersdk.WorkspaceStatusRunning {
40+
// Get listening ports for each agent.
41+
options.ListeningPorts = fetchListeningPorts(inv, client, workspace.LatestBuild.Resources...)
42+
}
43+
return cliui.WorkspaceResources(inv.Stdout, workspace.LatestBuild.Resources, options)
3344
},
3445
}
3546
}
47+
48+
func fetchListeningPorts(inv *serpent.Invocation, client *codersdk.Client, resources ...codersdk.WorkspaceResource) map[uuid.UUID]codersdk.WorkspaceAgentListeningPortsResponse {
49+
ports := make(map[uuid.UUID]codersdk.WorkspaceAgentListeningPortsResponse)
50+
var wg sync.WaitGroup
51+
var mu sync.Mutex
52+
for _, res := range resources {
53+
for _, agent := range res.Agents {
54+
wg.Add(1)
55+
go func() {
56+
defer wg.Done()
57+
lp, err := client.WorkspaceAgentListeningPorts(inv.Context(), agent.ID)
58+
if err != nil {
59+
cliui.Warnf(inv.Stderr, "Failed to get listening ports for agent %s: %v", agent.Name, err)
60+
}
61+
sort.Slice(lp.Ports, func(i, j int) bool {
62+
return lp.Ports[i].Port < lp.Ports[j].Port
63+
})
64+
mu.Lock()
65+
ports[agent.ID] = lp
66+
mu.Unlock()
67+
}()
68+
}
69+
}
70+
wg.Wait()
71+
return ports
72+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy