Skip to content

Commit 25f13a1

Browse files
committed
Merge branch 'main' of https://github.com/coder/coder into bq/refactor-daemons
2 parents b15d1b2 + 34b46f9 commit 25f13a1

28 files changed

+1094
-165
lines changed

cli/cliui/resources.go

Lines changed: 67 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type WorkspaceResourcesOptions struct {
2828
Title string
2929
ServerVersion string
3030
ListeningPorts map[uuid.UUID]codersdk.WorkspaceAgentListeningPortsResponse
31+
Devcontainers map[uuid.UUID]codersdk.WorkspaceAgentListContainersResponse
3132
}
3233

3334
// WorkspaceResources displays the connection status and tree-view of provided resources.
@@ -95,15 +96,11 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource
9596
// Display all agents associated with the resource.
9697
for index, agent := range resource.Agents {
9798
tableWriter.AppendRow(renderAgentRow(agent, index, totalAgents, options))
98-
if options.ListeningPorts != nil {
99-
if lp, ok := options.ListeningPorts[agent.ID]; ok && len(lp.Ports) > 0 {
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-
}
106-
}
99+
for _, row := range renderListeningPorts(options, agent.ID, index, totalAgents) {
100+
tableWriter.AppendRow(row)
101+
}
102+
for _, row := range renderDevcontainers(options, agent.ID, index, totalAgents) {
103+
tableWriter.AppendRow(row)
107104
}
108105
}
109106
tableWriter.AppendSeparator()
@@ -137,10 +134,28 @@ func renderAgentRow(agent codersdk.WorkspaceAgent, index, totalAgents int, optio
137134
return row
138135
}
139136

140-
func renderPortRow(port codersdk.WorkspaceAgentListeningPort, index, totalPorts int) table.Row {
137+
func renderListeningPorts(wro WorkspaceResourcesOptions, agentID uuid.UUID, idx, total int) []table.Row {
138+
var rows []table.Row
139+
if wro.ListeningPorts == nil {
140+
return []table.Row{}
141+
}
142+
lp, ok := wro.ListeningPorts[agentID]
143+
if !ok || len(lp.Ports) == 0 {
144+
return []table.Row{}
145+
}
146+
rows = append(rows, table.Row{
147+
fmt.Sprintf(" %s─ Open Ports", renderPipe(idx, total)),
148+
})
149+
for idx, port := range lp.Ports {
150+
rows = append(rows, renderPortRow(port, idx, len(lp.Ports)))
151+
}
152+
return rows
153+
}
154+
155+
func renderPortRow(port codersdk.WorkspaceAgentListeningPort, idx, total int) table.Row {
141156
var sb strings.Builder
142157
_, _ = sb.WriteString(" ")
143-
_, _ = sb.WriteString(renderPipe(index, totalPorts))
158+
_, _ = sb.WriteString(renderPipe(idx, total))
144159
_, _ = sb.WriteString("─ ")
145160
_, _ = sb.WriteString(pretty.Sprintf(DefaultStyles.Code, "%5d/%s", port.Port, port.Network))
146161
if port.ProcessName != "" {
@@ -149,6 +164,47 @@ func renderPortRow(port codersdk.WorkspaceAgentListeningPort, index, totalPorts
149164
return table.Row{sb.String()}
150165
}
151166

167+
func renderDevcontainers(wro WorkspaceResourcesOptions, agentID uuid.UUID, index, totalAgents int) []table.Row {
168+
var rows []table.Row
169+
if wro.Devcontainers == nil {
170+
return []table.Row{}
171+
}
172+
dc, ok := wro.Devcontainers[agentID]
173+
if !ok || len(dc.Containers) == 0 {
174+
return []table.Row{}
175+
}
176+
rows = append(rows, table.Row{
177+
fmt.Sprintf(" %s─ %s", renderPipe(index, totalAgents), "Devcontainers"),
178+
})
179+
for idx, container := range dc.Containers {
180+
rows = append(rows, renderDevcontainerRow(container, idx, len(dc.Containers)))
181+
}
182+
return rows
183+
}
184+
185+
func renderDevcontainerRow(container codersdk.WorkspaceAgentDevcontainer, 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))
198+
}
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
206+
}
207+
152208
func renderAgentStatus(agent codersdk.WorkspaceAgent) string {
153209
switch agent.Status {
154210
case codersdk.WorkspaceAgentConnecting:

cli/server.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2565,6 +2565,8 @@ func parseExternalAuthProvidersFromEnv(prefix string, environ []string) ([]coder
25652565
return providers, nil
25662566
}
25672567

2568+
var reInvalidPortAfterHost = regexp.MustCompile(`invalid port ".+" after host`)
2569+
25682570
// If the user provides a postgres URL with a password that contains special
25692571
// characters, the URL will be invalid. We need to escape the password so that
25702572
// the URL parse doesn't fail at the DB connector level.
@@ -2573,7 +2575,11 @@ func escapePostgresURLUserInfo(v string) (string, error) {
25732575
// I wish I could use errors.Is here, but this error is not declared as a
25742576
// variable in net/url. :(
25752577
if err != nil {
2576-
if strings.Contains(err.Error(), "net/url: invalid userinfo") {
2578+
// Warning: The parser may also fail with an "invalid port" error if the password contains special
2579+
// characters. It does not detect invalid user information but instead incorrectly reports an invalid port.
2580+
//
2581+
// See: https://github.com/coder/coder/issues/16319
2582+
if strings.Contains(err.Error(), "net/url: invalid userinfo") || reInvalidPortAfterHost.MatchString(err.Error()) {
25772583
// If the URL is invalid, we assume it is because the password contains
25782584
// special characters that need to be escaped.
25792585

cli/server_internal_test.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,13 +351,23 @@ func TestEscapePostgresURLUserInfo(t *testing.T) {
351351
output: "",
352352
err: xerrors.New("parse postgres url: parse \"postgres://local host:5432/coder\": invalid character \" \" in host name"),
353353
},
354+
{
355+
input: "postgres://coder:co?der@localhost:5432/coder",
356+
output: "postgres://coder:co%3Fder@localhost:5432/coder",
357+
err: nil,
358+
},
359+
{
360+
input: "postgres://coder:co#der@localhost:5432/coder",
361+
output: "postgres://coder:co%23der@localhost:5432/coder",
362+
err: nil,
363+
},
354364
}
355365
for _, tc := range testcases {
356366
tc := tc
357367
t.Run(tc.input, func(t *testing.T) {
358368
t.Parallel()
359369
o, err := escapePostgresURLUserInfo(tc.input)
360-
require.Equal(t, tc.output, o)
370+
assert.Equal(t, tc.output, o)
361371
if tc.err != nil {
362372
require.Error(t, err)
363373
require.EqualValues(t, tc.err.Error(), err.Error())

cli/show.go

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,18 @@ func (r *RootCmd) show() *serpent.Command {
3838
}
3939
if workspace.LatestBuild.Status == codersdk.WorkspaceStatusRunning {
4040
// Get listening ports for each agent.
41-
options.ListeningPorts = fetchListeningPorts(inv, client, workspace.LatestBuild.Resources...)
41+
ports, devcontainers := fetchRuntimeResources(inv, client, workspace.LatestBuild.Resources...)
42+
options.ListeningPorts = ports
43+
options.Devcontainers = devcontainers
4244
}
4345
return cliui.WorkspaceResources(inv.Stdout, workspace.LatestBuild.Resources, options)
4446
},
4547
}
4648
}
4749

48-
func fetchListeningPorts(inv *serpent.Invocation, client *codersdk.Client, resources ...codersdk.WorkspaceResource) map[uuid.UUID]codersdk.WorkspaceAgentListeningPortsResponse {
50+
func fetchRuntimeResources(inv *serpent.Invocation, client *codersdk.Client, resources ...codersdk.WorkspaceResource) (map[uuid.UUID]codersdk.WorkspaceAgentListeningPortsResponse, map[uuid.UUID]codersdk.WorkspaceAgentListContainersResponse) {
4951
ports := make(map[uuid.UUID]codersdk.WorkspaceAgentListeningPortsResponse)
52+
devcontainers := make(map[uuid.UUID]codersdk.WorkspaceAgentListContainersResponse)
5053
var wg sync.WaitGroup
5154
var mu sync.Mutex
5255
for _, res := range resources {
@@ -65,8 +68,23 @@ func fetchListeningPorts(inv *serpent.Invocation, client *codersdk.Client, resou
6568
ports[agent.ID] = lp
6669
mu.Unlock()
6770
}()
71+
wg.Add(1)
72+
go func() {
73+
defer wg.Done()
74+
dc, err := client.WorkspaceAgentListContainers(inv.Context(), agent.ID, map[string]string{
75+
// Labels set by VSCode Remote Containers and @devcontainers/cli.
76+
"devcontainer.config_file": "",
77+
"devcontainer.local_folder": "",
78+
})
79+
if err != nil {
80+
cliui.Warnf(inv.Stderr, "Failed to get devcontainers for agent %s: %v", agent.Name, err)
81+
}
82+
mu.Lock()
83+
devcontainers[agent.ID] = dc
84+
mu.Unlock()
85+
}()
6886
}
6987
}
7088
wg.Wait()
71-
return ports
89+
return ports, devcontainers
7290
}

coderd/database/dbauthz/dbauthz.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1930,6 +1930,33 @@ func (q *querier) GetParameterSchemasByJobID(ctx context.Context, jobID uuid.UUI
19301930
return q.db.GetParameterSchemasByJobID(ctx, jobID)
19311931
}
19321932

1933+
func (q *querier) GetPresetByWorkspaceBuildID(ctx context.Context, workspaceID uuid.UUID) (database.TemplateVersionPreset, error) {
1934+
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceTemplate); err != nil {
1935+
return database.TemplateVersionPreset{}, err
1936+
}
1937+
return q.db.GetPresetByWorkspaceBuildID(ctx, workspaceID)
1938+
}
1939+
1940+
func (q *querier) GetPresetParametersByTemplateVersionID(ctx context.Context, templateVersionID uuid.UUID) ([]database.TemplateVersionPresetParameter, error) {
1941+
// An actor can read template version presets if they can read the related template version.
1942+
_, err := q.GetTemplateVersionByID(ctx, templateVersionID)
1943+
if err != nil {
1944+
return nil, err
1945+
}
1946+
1947+
return q.db.GetPresetParametersByTemplateVersionID(ctx, templateVersionID)
1948+
}
1949+
1950+
func (q *querier) GetPresetsByTemplateVersionID(ctx context.Context, templateVersionID uuid.UUID) ([]database.TemplateVersionPreset, error) {
1951+
// An actor can read template version presets if they can read the related template version.
1952+
_, err := q.GetTemplateVersionByID(ctx, templateVersionID)
1953+
if err != nil {
1954+
return nil, err
1955+
}
1956+
1957+
return q.db.GetPresetsByTemplateVersionID(ctx, templateVersionID)
1958+
}
1959+
19331960
func (q *querier) GetPreviousTemplateVersion(ctx context.Context, arg database.GetPreviousTemplateVersionParams) (database.TemplateVersion, error) {
19341961
// An actor can read the previous template version if they can read the related template.
19351962
// If no linked template exists, we check if the actor can read *a* template.
@@ -3088,6 +3115,24 @@ func (q *querier) InsertOrganizationMember(ctx context.Context, arg database.Ins
30883115
return insert(q.log, q.auth, obj, q.db.InsertOrganizationMember)(ctx, arg)
30893116
}
30903117

3118+
func (q *querier) InsertPreset(ctx context.Context, arg database.InsertPresetParams) (database.TemplateVersionPreset, error) {
3119+
err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceTemplate)
3120+
if err != nil {
3121+
return database.TemplateVersionPreset{}, err
3122+
}
3123+
3124+
return q.db.InsertPreset(ctx, arg)
3125+
}
3126+
3127+
func (q *querier) InsertPresetParameters(ctx context.Context, arg database.InsertPresetParametersParams) ([]database.TemplateVersionPresetParameter, error) {
3128+
err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceTemplate)
3129+
if err != nil {
3130+
return nil, err
3131+
}
3132+
3133+
return q.db.InsertPresetParameters(ctx, arg)
3134+
}
3135+
30913136
// TODO: We need to create a ProvisionerJob resource type
30923137
func (q *querier) InsertProvisionerJob(ctx context.Context, arg database.InsertProvisionerJobParams) (database.ProvisionerJob, error) {
30933138
// if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceSystem); err != nil {

0 commit comments

Comments
 (0)