From 795ffda6669fcf78e9f3aaf3a0a69566d4f4fd46 Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 10 Feb 2025 14:21:59 +0000 Subject: [PATCH 1/2] fix(coderd/util/maps): ignore zero values in Subset --- coderd/util/maps/maps.go | 7 ++++++- coderd/util/maps/maps_test.go | 23 +++++++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/coderd/util/maps/maps.go b/coderd/util/maps/maps.go index 6d3d31717d33b..8aaa6669cb8af 100644 --- a/coderd/util/maps/maps.go +++ b/coderd/util/maps/maps.go @@ -8,9 +8,14 @@ import ( // Subset returns true if all the keys of a are present // in b and have the same values. +// If the corresponding value of a[k] is the zero value in +// b, Subset will skip comparing that value. +// This allows checking for the presence of map keys. func Subset[T, U comparable](a, b map[T]U) bool { + var uz U for ka, va := range a { - if vb, ok := b[ka]; !ok || va != vb { + ignoreZeroValue := va == uz + if vb, ok := b[ka]; !ok || (!ignoreZeroValue && va != vb) { return false } } diff --git a/coderd/util/maps/maps_test.go b/coderd/util/maps/maps_test.go index 1858d6467e89a..543c100c210a5 100644 --- a/coderd/util/maps/maps_test.go +++ b/coderd/util/maps/maps_test.go @@ -11,8 +11,9 @@ func TestSubset(t *testing.T) { t.Parallel() for idx, tc := range []struct { - a map[string]string - b map[string]string + a map[string]string + b map[string]string + // expected value from Subset expected bool }{ { @@ -50,6 +51,24 @@ func TestSubset(t *testing.T) { b: map[string]string{"a": "1", "b": "3"}, expected: false, }, + // Zero value + { + a: map[string]string{"a": "1", "b": ""}, + b: map[string]string{"a": "1", "b": "3"}, + expected: true, + }, + // Zero value, but the other way round + { + a: map[string]string{"a": "1", "b": "3"}, + b: map[string]string{"a": "1", "b": ""}, + expected: false, + }, + // Both zero values + { + a: map[string]string{"a": "1", "b": ""}, + b: map[string]string{"a": "1", "b": ""}, + expected: true, + }, } { tc := tc t.Run("#"+strconv.Itoa(idx), func(t *testing.T) { From eaab63fab10a46ca0154c1b1d1bd3ff31a67641a Mon Sep 17 00:00:00 2001 From: Cian Johnston Date: Mon, 10 Feb 2025 17:59:46 +0000 Subject: [PATCH 2/2] feat(cli): display running devcontainers in show command --- cli/cliui/resources.go | 78 +++++++++++++++++++++++++++++----- cli/show.go | 24 +++++++++-- coderd/workspaceagents_test.go | 8 +++- 3 files changed, 94 insertions(+), 16 deletions(-) diff --git a/cli/cliui/resources.go b/cli/cliui/resources.go index 8921033ddc9da..25277645ce96a 100644 --- a/cli/cliui/resources.go +++ b/cli/cliui/resources.go @@ -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: diff --git a/cli/show.go b/cli/show.go index 7da747d6fffa1..f2d3df3ecc3c5 100644 --- a/cli/show.go +++ b/cli/show.go @@ -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 } diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index f7a3513d4f655..7a051ef233f1e 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -1076,7 +1076,8 @@ func TestWorkspaceAgentContainers(t *testing.T) { pool, err := dockertest.NewPool("") require.NoError(t, err, "Could not connect to docker") testLabels := map[string]string{ - "com.coder.test": uuid.New().String(), + "com.coder.test": uuid.New().String(), + "com.coder.empty": "", } ct, err := pool.RunWithOptions(&dockertest.RunOptions{ Repository: "busybox", @@ -1097,7 +1098,10 @@ func TestWorkspaceAgentContainers(t *testing.T) { Repository: "busybox", Tag: "latest", Cmd: []string{"sleep", "infinity"}, - Labels: map[string]string{"com.coder.test": "ignoreme"}, + Labels: map[string]string{ + "com.coder.test": "ignoreme", + "com.coder.empty": "", + }, }, func(config *docker.HostConfig) { config.AutoRemove = true config.RestartPolicy = docker.RestartPolicy{Name: "no"} 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