From 2d8154e98d46f3ad51848b0bbb965965c98e9d90 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Wed, 5 Feb 2025 22:21:49 +0000 Subject: [PATCH 1/2] feat(cli): display open ports in coder show --- cli/cliui/resources.go | 85 ++++++++++++++++++++++++++++++------------ cli/show.go | 41 +++++++++++++++++++- 2 files changed, 100 insertions(+), 26 deletions(-) diff --git a/cli/cliui/resources.go b/cli/cliui/resources.go index a9204c968c10a..d0be238dc51f6 100644 --- a/cli/cliui/resources.go +++ b/cli/cliui/resources.go @@ -5,7 +5,9 @@ import ( "io" "sort" "strconv" + "strings" + "github.com/google/uuid" "github.com/jedib0t/go-pretty/v6/table" "golang.org/x/mod/semver" @@ -14,12 +16,18 @@ import ( "github.com/coder/pretty" ) +var ( + pipeMid = "├" + pipeEnd = "└" +) + type WorkspaceResourcesOptions struct { WorkspaceName string HideAgentState bool HideAccess bool Title string ServerVersion string + ListeningPorts map[uuid.UUID]codersdk.WorkspaceAgentListeningPortsResponse } // WorkspaceResources displays the connection status and tree-view of provided resources. @@ -86,32 +94,17 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource }) // Display all agents associated with the resource. for index, agent := range resource.Agents { - pipe := "├" - if index == len(resource.Agents)-1 { - pipe = "└" - } - row := table.Row{ - // These tree from a resource! - fmt.Sprintf("%s─ %s (%s, %s)", pipe, agent.Name, agent.OperatingSystem, agent.Architecture), - } - if !options.HideAgentState { - var agentStatus, agentHealth, agentVersion string - if !options.HideAgentState { - agentStatus = renderAgentStatus(agent) - agentHealth = renderAgentHealth(agent) - agentVersion = renderAgentVersion(agent.Version, options.ServerVersion) - } - row = append(row, agentStatus, agentHealth, agentVersion) - } - if !options.HideAccess { - sshCommand := "coder ssh " + options.WorkspaceName - if totalAgents > 1 { - sshCommand += "." + agent.Name + tableWriter.AppendRow(renderAgentRow(agent, index, totalAgents, options)) + if options.ListeningPorts != nil { + if lp, ok := options.ListeningPorts[agent.ID]; ok { + tableWriter.AppendRow(table.Row{ + fmt.Sprintf(" %s─ %s", renderPipe(index, totalAgents), "Open Ports"), + }) + for _, port := range lp.Ports { + tableWriter.AppendRow(renderPortRow(port, index, totalAgents)) + } } - sshCommand = pretty.Sprint(DefaultStyles.Code, sshCommand) - row = append(row, sshCommand) } - tableWriter.AppendRow(row) } tableWriter.AppendSeparator() } @@ -119,6 +112,43 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource return err } +func renderAgentRow(agent codersdk.WorkspaceAgent, index, totalAgents int, options WorkspaceResourcesOptions) table.Row { + row := table.Row{ + // These tree from a resource! + fmt.Sprintf("%s─ %s (%s, %s)", renderPipe(index, totalAgents), agent.Name, agent.OperatingSystem, agent.Architecture), + } + if !options.HideAgentState { + var agentStatus, agentHealth, agentVersion string + if !options.HideAgentState { + agentStatus = renderAgentStatus(agent) + agentHealth = renderAgentHealth(agent) + agentVersion = renderAgentVersion(agent.Version, options.ServerVersion) + } + row = append(row, agentStatus, agentHealth, agentVersion) + } + if !options.HideAccess { + sshCommand := "coder ssh " + options.WorkspaceName + if totalAgents > 1 { + sshCommand += "." + agent.Name + } + sshCommand = pretty.Sprint(DefaultStyles.Code, sshCommand) + row = append(row, sshCommand) + } + return row +} + +func renderPortRow(port codersdk.WorkspaceAgentListeningPort, index, totalPorts int) table.Row { + var sb strings.Builder + _, _ = sb.WriteString(" ") + _, _ = sb.WriteString(renderPipe(index, totalPorts)) + _, _ = sb.WriteString("─ ") + _, _ = sb.WriteString(pretty.Sprintf(DefaultStyles.Code, "%5d/%s", port.Port, port.Network)) + if port.ProcessName != "" { + _, _ = sb.WriteString(pretty.Sprintf(DefaultStyles.Keyword, " [%s]", port.ProcessName)) + } + return table.Row{sb.String()} +} + func renderAgentStatus(agent codersdk.WorkspaceAgent) string { switch agent.Status { case codersdk.WorkspaceAgentConnecting: @@ -163,3 +193,10 @@ func renderAgentVersion(agentVersion, serverVersion string) string { } return pretty.Sprint(DefaultStyles.Keyword, agentVersion) } + +func renderPipe(idx, total int) string { + if idx == total-1 { + return pipeEnd + } + return pipeMid +} diff --git a/cli/show.go b/cli/show.go index 00c50292d69c1..7da747d6fffa1 100644 --- a/cli/show.go +++ b/cli/show.go @@ -1,8 +1,13 @@ package cli import ( + "sort" + "sync" + "golang.org/x/xerrors" + "github.com/google/uuid" + "github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/codersdk" "github.com/coder/serpent" @@ -26,10 +31,42 @@ func (r *RootCmd) show() *serpent.Command { if err != nil { return xerrors.Errorf("get workspace: %w", err) } - return cliui.WorkspaceResources(inv.Stdout, workspace.LatestBuild.Resources, cliui.WorkspaceResourcesOptions{ + + options := cliui.WorkspaceResourcesOptions{ WorkspaceName: workspace.Name, ServerVersion: buildInfo.Version, - }) + } + if workspace.LatestBuild.Status == codersdk.WorkspaceStatusRunning { + // Get listening ports for each agent. + options.ListeningPorts = fetchListeningPorts(inv, client, workspace.LatestBuild.Resources...) + } + return cliui.WorkspaceResources(inv.Stdout, workspace.LatestBuild.Resources, options) }, } } + +func fetchListeningPorts(inv *serpent.Invocation, client *codersdk.Client, resources ...codersdk.WorkspaceResource) map[uuid.UUID]codersdk.WorkspaceAgentListeningPortsResponse { + ports := make(map[uuid.UUID]codersdk.WorkspaceAgentListeningPortsResponse) + var wg sync.WaitGroup + var mu sync.Mutex + for _, res := range resources { + for _, agent := range res.Agents { + wg.Add(1) + go func() { + defer wg.Done() + lp, err := client.WorkspaceAgentListeningPorts(inv.Context(), agent.ID) + if err != nil { + cliui.Warnf(inv.Stderr, "Failed to get listening ports for agent %s: %v", agent.Name, err) + } + sort.Slice(lp.Ports, func(i, j int) bool { + return lp.Ports[i].Port < lp.Ports[j].Port + }) + mu.Lock() + ports[agent.ID] = lp + mu.Unlock() + }() + } + } + wg.Wait() + return ports +} From 93b13c1184c7d44c2d8986775d9340eeb2d33b25 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 10 Feb 2025 12:08:02 +0000 Subject: [PATCH 2/2] fixup! feat(cli): display open ports in coder show --- cli/cliui/resources.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/cliui/resources.go b/cli/cliui/resources.go index d0be238dc51f6..8921033ddc9da 100644 --- a/cli/cliui/resources.go +++ b/cli/cliui/resources.go @@ -96,7 +96,7 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource for index, agent := range resource.Agents { tableWriter.AppendRow(renderAgentRow(agent, index, totalAgents, options)) if options.ListeningPorts != nil { - if lp, ok := options.ListeningPorts[agent.ID]; ok { + if lp, ok := options.ListeningPorts[agent.ID]; ok && len(lp.Ports) > 0 { tableWriter.AppendRow(table.Row{ fmt.Sprintf(" %s─ %s", renderPipe(index, totalAgents), "Open Ports"), }) 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