feat(cli): display devcontainers in show command (#16515)

Displays running devcontainers into the `coder show` CLI command.
This commit is contained in:
Cian Johnston
2025-02-11 09:20:55 +00:00
committed by GitHub
parent 72f62578c1
commit 4867cbe53d
5 changed files with 121 additions and 19 deletions
+67 -11
View File
@@ -28,6 +28,7 @@ type WorkspaceResourcesOptions struct {
Title string
ServerVersion string
ListeningPorts map[uuid.UUID]codersdk.WorkspaceAgentListeningPortsResponse
Devcontainers map[uuid.UUID]codersdk.WorkspaceAgentListContainersResponse
}
// WorkspaceResources displays the connection status and tree-view of provided resources.
@@ -95,15 +96,11 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource
// Display all agents associated with the resource.
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 && len(lp.Ports) > 0 {
tableWriter.AppendRow(table.Row{
fmt.Sprintf(" %s─ %s", renderPipe(index, totalAgents), "Open Ports"),
})
for _, port := range lp.Ports {
tableWriter.AppendRow(renderPortRow(port, index, totalAgents))
}
}
for _, row := range renderListeningPorts(options, agent.ID, index, totalAgents) {
tableWriter.AppendRow(row)
}
for _, row := range renderDevcontainers(options, agent.ID, index, totalAgents) {
tableWriter.AppendRow(row)
}
}
tableWriter.AppendSeparator()
@@ -137,10 +134,28 @@ func renderAgentRow(agent codersdk.WorkspaceAgent, index, totalAgents int, optio
return row
}
func renderPortRow(port codersdk.WorkspaceAgentListeningPort, index, totalPorts int) table.Row {
func renderListeningPorts(wro WorkspaceResourcesOptions, agentID uuid.UUID, idx, total int) []table.Row {
var rows []table.Row
if wro.ListeningPorts == nil {
return []table.Row{}
}
lp, ok := wro.ListeningPorts[agentID]
if !ok || len(lp.Ports) == 0 {
return []table.Row{}
}
rows = append(rows, table.Row{
fmt.Sprintf(" %s─ Open Ports", renderPipe(idx, total)),
})
for idx, port := range lp.Ports {
rows = append(rows, renderPortRow(port, idx, len(lp.Ports)))
}
return rows
}
func renderPortRow(port codersdk.WorkspaceAgentListeningPort, idx, total int) table.Row {
var sb strings.Builder
_, _ = sb.WriteString(" ")
_, _ = sb.WriteString(renderPipe(index, totalPorts))
_, _ = sb.WriteString(renderPipe(idx, total))
_, _ = sb.WriteString("─ ")
_, _ = sb.WriteString(pretty.Sprintf(DefaultStyles.Code, "%5d/%s", port.Port, port.Network))
if port.ProcessName != "" {
@@ -149,6 +164,47 @@ func renderPortRow(port codersdk.WorkspaceAgentListeningPort, index, totalPorts
return table.Row{sb.String()}
}
func renderDevcontainers(wro WorkspaceResourcesOptions, agentID uuid.UUID, index, totalAgents int) []table.Row {
var rows []table.Row
if wro.Devcontainers == nil {
return []table.Row{}
}
dc, ok := wro.Devcontainers[agentID]
if !ok || len(dc.Containers) == 0 {
return []table.Row{}
}
rows = append(rows, table.Row{
fmt.Sprintf(" %s─ %s", renderPipe(index, totalAgents), "Devcontainers"),
})
for idx, container := range dc.Containers {
rows = append(rows, renderDevcontainerRow(container, idx, len(dc.Containers)))
}
return rows
}
func renderDevcontainerRow(container codersdk.WorkspaceAgentDevcontainer, index, total int) table.Row {
var row table.Row
var sb strings.Builder
_, _ = sb.WriteString(" ")
_, _ = sb.WriteString(renderPipe(index, total))
_, _ = sb.WriteString("─ ")
_, _ = sb.WriteString(pretty.Sprintf(DefaultStyles.Code, "%s", container.FriendlyName))
row = append(row, sb.String())
sb.Reset()
if container.Running {
_, _ = sb.WriteString(pretty.Sprintf(DefaultStyles.Keyword, "(%s)", container.Status))
} else {
_, _ = sb.WriteString(pretty.Sprintf(DefaultStyles.Error, "(%s)", container.Status))
}
row = append(row, sb.String())
sb.Reset()
// "health" is not applicable here.
row = append(row, sb.String())
_, _ = sb.WriteString(container.Image)
row = append(row, sb.String())
return row
}
func renderAgentStatus(agent codersdk.WorkspaceAgent) string {
switch agent.Status {
case codersdk.WorkspaceAgentConnecting:
+21 -3
View File
@@ -38,15 +38,18 @@ func (r *RootCmd) show() *serpent.Command {
}
if workspace.LatestBuild.Status == codersdk.WorkspaceStatusRunning {
// Get listening ports for each agent.
options.ListeningPorts = fetchListeningPorts(inv, client, workspace.LatestBuild.Resources...)
ports, devcontainers := fetchRuntimeResources(inv, client, workspace.LatestBuild.Resources...)
options.ListeningPorts = ports
options.Devcontainers = devcontainers
}
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 {
func fetchRuntimeResources(inv *serpent.Invocation, client *codersdk.Client, resources ...codersdk.WorkspaceResource) (map[uuid.UUID]codersdk.WorkspaceAgentListeningPortsResponse, map[uuid.UUID]codersdk.WorkspaceAgentListContainersResponse) {
ports := make(map[uuid.UUID]codersdk.WorkspaceAgentListeningPortsResponse)
devcontainers := make(map[uuid.UUID]codersdk.WorkspaceAgentListContainersResponse)
var wg sync.WaitGroup
var mu sync.Mutex
for _, res := range resources {
@@ -65,8 +68,23 @@ func fetchListeningPorts(inv *serpent.Invocation, client *codersdk.Client, resou
ports[agent.ID] = lp
mu.Unlock()
}()
wg.Add(1)
go func() {
defer wg.Done()
dc, err := client.WorkspaceAgentListContainers(inv.Context(), agent.ID, map[string]string{
// Labels set by VSCode Remote Containers and @devcontainers/cli.
"devcontainer.config_file": "",
"devcontainer.local_folder": "",
})
if err != nil {
cliui.Warnf(inv.Stderr, "Failed to get devcontainers for agent %s: %v", agent.Name, err)
}
mu.Lock()
devcontainers[agent.ID] = dc
mu.Unlock()
}()
}
}
wg.Wait()
return ports
return ports, devcontainers
}