Skip to content

Commit 1af39a8

Browse files
committed
Merge branch 'main' into dev-container-ga
2 parents e8cff18 + 39ed0c3 commit 1af39a8

Some content is hidden

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

44 files changed

+1030
-454
lines changed

.github/workflows/release.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -693,6 +693,8 @@ jobs:
693693
gsutil -h "Cache-Control:no-cache,max-age=0" cp build/helm/provisioner_helm_${version}.tgz gs://helm.coder.com/v2
694694
gsutil -h "Cache-Control:no-cache,max-age=0" cp build/helm/index.yaml gs://helm.coder.com/v2
695695
gsutil -h "Cache-Control:no-cache,max-age=0" cp helm/artifacthub-repo.yml gs://helm.coder.com/v2
696+
helm push build/coder_helm_${version}.tgz oci://ghcr.io/coder/chart
697+
helm push build/provisioner_helm_${version}.tgz oci://ghcr.io/coder/chart
696698
697699
- name: Upload artifacts to actions (if dry-run)
698700
if: ${{ inputs.dry_run }}

agent/agentcontainers/devcontainercli.go

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -106,63 +106,63 @@ type DevcontainerCLI interface {
106106

107107
// DevcontainerCLIUpOptions are options for the devcontainer CLI Up
108108
// command.
109-
type DevcontainerCLIUpOptions func(*devcontainerCLIUpConfig)
109+
type DevcontainerCLIUpOptions func(*DevcontainerCLIUpConfig)
110110

111-
type devcontainerCLIUpConfig struct {
112-
args []string // Additional arguments for the Up command.
113-
stdout io.Writer
114-
stderr io.Writer
111+
type DevcontainerCLIUpConfig struct {
112+
Args []string // Additional arguments for the Up command.
113+
Stdout io.Writer
114+
Stderr io.Writer
115115
}
116116

117117
// WithRemoveExistingContainer is an option to remove the existing
118118
// container.
119119
func WithRemoveExistingContainer() DevcontainerCLIUpOptions {
120-
return func(o *devcontainerCLIUpConfig) {
121-
o.args = append(o.args, "--remove-existing-container")
120+
return func(o *DevcontainerCLIUpConfig) {
121+
o.Args = append(o.Args, "--remove-existing-container")
122122
}
123123
}
124124

125125
// WithUpOutput sets additional stdout and stderr writers for logs
126126
// during Up operations.
127127
func WithUpOutput(stdout, stderr io.Writer) DevcontainerCLIUpOptions {
128-
return func(o *devcontainerCLIUpConfig) {
129-
o.stdout = stdout
130-
o.stderr = stderr
128+
return func(o *DevcontainerCLIUpConfig) {
129+
o.Stdout = stdout
130+
o.Stderr = stderr
131131
}
132132
}
133133

134134
// DevcontainerCLIExecOptions are options for the devcontainer CLI Exec
135135
// command.
136-
type DevcontainerCLIExecOptions func(*devcontainerCLIExecConfig)
136+
type DevcontainerCLIExecOptions func(*DevcontainerCLIExecConfig)
137137

138-
type devcontainerCLIExecConfig struct {
139-
args []string // Additional arguments for the Exec command.
140-
stdout io.Writer
141-
stderr io.Writer
138+
type DevcontainerCLIExecConfig struct {
139+
Args []string // Additional arguments for the Exec command.
140+
Stdout io.Writer
141+
Stderr io.Writer
142142
}
143143

144144
// WithExecOutput sets additional stdout and stderr writers for logs
145145
// during Exec operations.
146146
func WithExecOutput(stdout, stderr io.Writer) DevcontainerCLIExecOptions {
147-
return func(o *devcontainerCLIExecConfig) {
148-
o.stdout = stdout
149-
o.stderr = stderr
147+
return func(o *DevcontainerCLIExecConfig) {
148+
o.Stdout = stdout
149+
o.Stderr = stderr
150150
}
151151
}
152152

153153
// WithExecContainerID sets the container ID to target a specific
154154
// container.
155155
func WithExecContainerID(id string) DevcontainerCLIExecOptions {
156-
return func(o *devcontainerCLIExecConfig) {
157-
o.args = append(o.args, "--container-id", id)
156+
return func(o *DevcontainerCLIExecConfig) {
157+
o.Args = append(o.Args, "--container-id", id)
158158
}
159159
}
160160

161161
// WithRemoteEnv sets environment variables for the Exec command.
162162
func WithRemoteEnv(env ...string) DevcontainerCLIExecOptions {
163-
return func(o *devcontainerCLIExecConfig) {
163+
return func(o *DevcontainerCLIExecConfig) {
164164
for _, e := range env {
165-
o.args = append(o.args, "--remote-env", e)
165+
o.Args = append(o.Args, "--remote-env", e)
166166
}
167167
}
168168
}
@@ -185,8 +185,8 @@ func WithReadConfigOutput(stdout, stderr io.Writer) DevcontainerCLIReadConfigOpt
185185
}
186186
}
187187

188-
func applyDevcontainerCLIUpOptions(opts []DevcontainerCLIUpOptions) devcontainerCLIUpConfig {
189-
conf := devcontainerCLIUpConfig{stdout: io.Discard, stderr: io.Discard}
188+
func applyDevcontainerCLIUpOptions(opts []DevcontainerCLIUpOptions) DevcontainerCLIUpConfig {
189+
conf := DevcontainerCLIUpConfig{Stdout: io.Discard, Stderr: io.Discard}
190190
for _, opt := range opts {
191191
if opt != nil {
192192
opt(&conf)
@@ -195,8 +195,8 @@ func applyDevcontainerCLIUpOptions(opts []DevcontainerCLIUpOptions) devcontainer
195195
return conf
196196
}
197197

198-
func applyDevcontainerCLIExecOptions(opts []DevcontainerCLIExecOptions) devcontainerCLIExecConfig {
199-
conf := devcontainerCLIExecConfig{stdout: io.Discard, stderr: io.Discard}
198+
func applyDevcontainerCLIExecOptions(opts []DevcontainerCLIExecOptions) DevcontainerCLIExecConfig {
199+
conf := DevcontainerCLIExecConfig{Stdout: io.Discard, Stderr: io.Discard}
200200
for _, opt := range opts {
201201
if opt != nil {
202202
opt(&conf)
@@ -241,7 +241,7 @@ func (d *devcontainerCLI) Up(ctx context.Context, workspaceFolder, configPath st
241241
if configPath != "" {
242242
args = append(args, "--config", configPath)
243243
}
244-
args = append(args, conf.args...)
244+
args = append(args, conf.Args...)
245245
cmd := d.execer.CommandContext(ctx, "devcontainer", args...)
246246

247247
// Capture stdout for parsing and stream logs for both default and provided writers.
@@ -251,14 +251,14 @@ func (d *devcontainerCLI) Up(ctx context.Context, workspaceFolder, configPath st
251251
&devcontainerCLILogWriter{
252252
ctx: ctx,
253253
logger: logger.With(slog.F("stdout", true)),
254-
writer: conf.stdout,
254+
writer: conf.Stdout,
255255
},
256256
)
257257
// Stream stderr logs and provided writer if any.
258258
cmd.Stderr = &devcontainerCLILogWriter{
259259
ctx: ctx,
260260
logger: logger.With(slog.F("stderr", true)),
261-
writer: conf.stderr,
261+
writer: conf.Stderr,
262262
}
263263

264264
if err := cmd.Run(); err != nil {
@@ -293,17 +293,17 @@ func (d *devcontainerCLI) Exec(ctx context.Context, workspaceFolder, configPath
293293
if configPath != "" {
294294
args = append(args, "--config", configPath)
295295
}
296-
args = append(args, conf.args...)
296+
args = append(args, conf.Args...)
297297
args = append(args, cmd)
298298
args = append(args, cmdArgs...)
299299
c := d.execer.CommandContext(ctx, "devcontainer", args...)
300300

301-
c.Stdout = io.MultiWriter(conf.stdout, &devcontainerCLILogWriter{
301+
c.Stdout = io.MultiWriter(conf.Stdout, &devcontainerCLILogWriter{
302302
ctx: ctx,
303303
logger: logger.With(slog.F("stdout", true)),
304304
writer: io.Discard,
305305
})
306-
c.Stderr = io.MultiWriter(conf.stderr, &devcontainerCLILogWriter{
306+
c.Stderr = io.MultiWriter(conf.Stderr, &devcontainerCLILogWriter{
307307
ctx: ctx,
308308
logger: logger.With(slog.F("stderr", true)),
309309
writer: io.Discard,

cli/cliui/resources.go

Lines changed: 121 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"golang.org/x/mod/semver"
1313

1414
"github.com/coder/coder/v2/coderd/database/dbtime"
15+
"github.com/coder/coder/v2/coderd/util/slice"
1516
"github.com/coder/coder/v2/codersdk"
1617
"github.com/coder/pretty"
1718
)
@@ -29,6 +30,7 @@ type WorkspaceResourcesOptions struct {
2930
ServerVersion string
3031
ListeningPorts map[uuid.UUID]codersdk.WorkspaceAgentListeningPortsResponse
3132
Devcontainers map[uuid.UUID]codersdk.WorkspaceAgentListContainersResponse
33+
ShowDetails bool
3234
}
3335

3436
// WorkspaceResources displays the connection status and tree-view of provided resources.
@@ -69,7 +71,11 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource
6971

7072
totalAgents := 0
7173
for _, resource := range resources {
72-
totalAgents += len(resource.Agents)
74+
for _, agent := range resource.Agents {
75+
if !agent.ParentID.Valid {
76+
totalAgents++
77+
}
78+
}
7379
}
7480

7581
for _, resource := range resources {
@@ -94,12 +100,15 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource
94100
"",
95101
})
96102
// Display all agents associated with the resource.
97-
for index, agent := range resource.Agents {
103+
agents := slice.Filter(resource.Agents, func(agent codersdk.WorkspaceAgent) bool {
104+
return !agent.ParentID.Valid
105+
})
106+
for index, agent := range agents {
98107
tableWriter.AppendRow(renderAgentRow(agent, index, totalAgents, options))
99108
for _, row := range renderListeningPorts(options, agent.ID, index, totalAgents) {
100109
tableWriter.AppendRow(row)
101110
}
102-
for _, row := range renderDevcontainers(options, agent.ID, index, totalAgents) {
111+
for _, row := range renderDevcontainers(resources, options, agent.ID, index, totalAgents) {
103112
tableWriter.AppendRow(row)
104113
}
105114
}
@@ -125,7 +134,7 @@ func renderAgentRow(agent codersdk.WorkspaceAgent, index, totalAgents int, optio
125134
}
126135
if !options.HideAccess {
127136
sshCommand := "coder ssh " + options.WorkspaceName
128-
if totalAgents > 1 {
137+
if totalAgents > 1 || len(options.Devcontainers) > 0 {
129138
sshCommand += "." + agent.Name
130139
}
131140
sshCommand = pretty.Sprint(DefaultStyles.Code, sshCommand)
@@ -164,45 +173,129 @@ func renderPortRow(port codersdk.WorkspaceAgentListeningPort, idx, total int) ta
164173
return table.Row{sb.String()}
165174
}
166175

167-
func renderDevcontainers(wro WorkspaceResourcesOptions, agentID uuid.UUID, index, totalAgents int) []table.Row {
176+
func renderDevcontainers(resources []codersdk.WorkspaceResource, wro WorkspaceResourcesOptions, agentID uuid.UUID, index, totalAgents int) []table.Row {
168177
var rows []table.Row
169178
if wro.Devcontainers == nil {
170179
return []table.Row{}
171180
}
172181
dc, ok := wro.Devcontainers[agentID]
173-
if !ok || len(dc.Containers) == 0 {
182+
if !ok || len(dc.Devcontainers) == 0 {
174183
return []table.Row{}
175184
}
176185
rows = append(rows, table.Row{
177186
fmt.Sprintf(" %s─ %s", renderPipe(index, totalAgents), "Devcontainers"),
178187
})
179-
for idx, container := range dc.Containers {
180-
rows = append(rows, renderDevcontainerRow(container, idx, len(dc.Containers)))
188+
for idx, devcontainer := range dc.Devcontainers {
189+
rows = append(rows, renderDevcontainerRow(resources, devcontainer, idx, len(dc.Devcontainers), wro)...)
181190
}
182191
return rows
183192
}
184193

185-
func renderDevcontainerRow(container codersdk.WorkspaceAgentContainer, index, total int) table.Row {
186-
var row table.Row
187-
var sb strings.Builder
188-
_, _ = sb.WriteString(" ")
189-
_, _ = sb.WriteString(renderPipe(index, total))
190-
_, _ = sb.WriteString("─ ")
191-
_, _ = sb.WriteString(pretty.Sprintf(DefaultStyles.Code, "%s", container.FriendlyName))
192-
row = append(row, sb.String())
193-
sb.Reset()
194-
if container.Running {
195-
_, _ = sb.WriteString(pretty.Sprintf(DefaultStyles.Keyword, "(%s)", container.Status))
196-
} else {
197-
_, _ = sb.WriteString(pretty.Sprintf(DefaultStyles.Error, "(%s)", container.Status))
194+
func renderDevcontainerRow(resources []codersdk.WorkspaceResource, devcontainer codersdk.WorkspaceAgentDevcontainer, index, total int, wro WorkspaceResourcesOptions) []table.Row {
195+
var rows []table.Row
196+
197+
// If the devcontainer is running and has an associated agent, we want to
198+
// display the agent's details. Otherwise, we just display the devcontainer
199+
// name and status.
200+
var subAgent *codersdk.WorkspaceAgent
201+
displayName := devcontainer.Name
202+
if devcontainer.Agent != nil && devcontainer.Status == codersdk.WorkspaceAgentDevcontainerStatusRunning {
203+
for _, resource := range resources {
204+
if agent, found := slice.Find(resource.Agents, func(agent codersdk.WorkspaceAgent) bool {
205+
return agent.ID == devcontainer.Agent.ID
206+
}); found {
207+
subAgent = &agent
208+
break
209+
}
210+
}
211+
if subAgent != nil {
212+
displayName = subAgent.Name
213+
displayName += fmt.Sprintf(" (%s, %s)", subAgent.OperatingSystem, subAgent.Architecture)
214+
}
215+
}
216+
217+
if devcontainer.Container != nil {
218+
displayName += " " + pretty.Sprint(DefaultStyles.Keyword, "["+devcontainer.Container.FriendlyName+"]")
219+
}
220+
221+
// Build the main row.
222+
row := table.Row{
223+
fmt.Sprintf(" %s─ %s", renderPipe(index, total), displayName),
224+
}
225+
226+
// Add status, health, and version columns.
227+
if !wro.HideAgentState {
228+
if subAgent != nil {
229+
row = append(row, renderAgentStatus(*subAgent))
230+
row = append(row, renderAgentHealth(*subAgent))
231+
row = append(row, renderAgentVersion(subAgent.Version, wro.ServerVersion))
232+
} else {
233+
row = append(row, renderDevcontainerStatus(devcontainer.Status))
234+
row = append(row, "") // No health for devcontainer without agent.
235+
row = append(row, "") // No version for devcontainer without agent.
236+
}
237+
}
238+
239+
// Add access column.
240+
if !wro.HideAccess {
241+
if subAgent != nil {
242+
accessString := fmt.Sprintf("coder ssh %s.%s", wro.WorkspaceName, subAgent.Name)
243+
row = append(row, pretty.Sprint(DefaultStyles.Code, accessString))
244+
} else {
245+
row = append(row, "") // No access for devcontainers without agent.
246+
}
247+
}
248+
249+
rows = append(rows, row)
250+
251+
// Add error message if present.
252+
if errorMessage := devcontainer.Error; errorMessage != "" {
253+
// Cap error message length for display.
254+
if !wro.ShowDetails && len(errorMessage) > 80 {
255+
errorMessage = errorMessage[:79] + "…"
256+
}
257+
errorRow := table.Row{
258+
" × " + pretty.Sprint(DefaultStyles.Error, errorMessage),
259+
"",
260+
"",
261+
"",
262+
}
263+
if !wro.HideAccess {
264+
errorRow = append(errorRow, "")
265+
}
266+
rows = append(rows, errorRow)
267+
}
268+
269+
// Add listening ports for the devcontainer agent.
270+
if subAgent != nil {
271+
portRows := renderListeningPorts(wro, subAgent.ID, index, total)
272+
for _, portRow := range portRows {
273+
// Adjust indentation for ports under devcontainer agent.
274+
if len(portRow) > 0 {
275+
if str, ok := portRow[0].(string); ok {
276+
portRow[0] = " " + str // Add extra indentation.
277+
}
278+
}
279+
rows = append(rows, portRow)
280+
}
281+
}
282+
283+
return rows
284+
}
285+
286+
func renderDevcontainerStatus(status codersdk.WorkspaceAgentDevcontainerStatus) string {
287+
switch status {
288+
case codersdk.WorkspaceAgentDevcontainerStatusRunning:
289+
return pretty.Sprint(DefaultStyles.Keyword, "▶ running")
290+
case codersdk.WorkspaceAgentDevcontainerStatusStopped:
291+
return pretty.Sprint(DefaultStyles.Placeholder, "⏹ stopped")
292+
case codersdk.WorkspaceAgentDevcontainerStatusStarting:
293+
return pretty.Sprint(DefaultStyles.Warn, "⧗ starting")
294+
case codersdk.WorkspaceAgentDevcontainerStatusError:
295+
return pretty.Sprint(DefaultStyles.Error, "✘ error")
296+
default:
297+
return pretty.Sprint(DefaultStyles.Placeholder, "○ "+string(status))
198298
}
199-
row = append(row, sb.String())
200-
sb.Reset()
201-
// "health" is not applicable here.
202-
row = append(row, sb.String())
203-
_, _ = sb.WriteString(container.Image)
204-
row = append(row, sb.String())
205-
return row
206299
}
207300

208301
func renderAgentStatus(agent codersdk.WorkspaceAgent) string {

cli/exp_rpty.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func handleRPTY(inv *serpent.Invocation, client *codersdk.Client, args handleRPT
9797
reconnectID = uuid.New()
9898
}
9999

100-
ws, agt, err := getWorkspaceAndAgent(ctx, inv, client, true, args.NamedWorkspace)
100+
ws, agt, _, err := getWorkspaceAndAgent(ctx, inv, client, true, args.NamedWorkspace)
101101
if err != nil {
102102
return err
103103
}

0 commit comments

Comments
 (0)