Skip to content

Commit 7c5de39

Browse files
committed
Merge remote-tracking branch 'origin/main' into jjs/presets
2 parents 6e7ccfd + 34b46f9 commit 7c5de39

File tree

14 files changed

+179
-48
lines changed

14 files changed

+179
-48
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_test.go

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -860,7 +860,6 @@ func (s *MethodTestSuite) TestOrganization() {
860860
rbac.ResourceOrganizationMember.InOrg(o.ID).WithID(u.ID), policy.ActionCreate)
861861
}))
862862
s.Run("InsertPreset", s.Subtest(func(db database.Store, check *expects) {
863-
ctx := context.Background()
864863
org := dbgen.Organization(s.T(), db, database.Organization{})
865864
user := dbgen.User(s.T(), db, database.User{})
866865
template := dbgen.Template(s.T(), db, database.Template{
@@ -887,11 +886,10 @@ func (s *MethodTestSuite) TestOrganization() {
887886
JobID: job.ID,
888887
})
889888
insertPresetParams := database.InsertPresetParams{
889+
ID: uuid.New(),
890890
TemplateVersionID: workspaceBuild.TemplateVersionID,
891891
Name: "test",
892892
}
893-
_, err := db.InsertPreset(ctx, insertPresetParams)
894-
require.NoError(s.T(), err)
895893
check.Args(insertPresetParams).Asserts(rbac.ResourceTemplate, policy.ActionUpdate)
896894
}))
897895
s.Run("InsertPresetParameters", s.Subtest(func(db database.Store, check *expects) {
@@ -931,8 +929,6 @@ func (s *MethodTestSuite) TestOrganization() {
931929
Names: []string{"test"},
932930
Values: []string{"test"},
933931
}
934-
_, err = db.InsertPresetParameters(context.Background(), insertPresetParametersParams)
935-
require.NoError(s.T(), err)
936932
check.Args(insertPresetParametersParams).Asserts(rbac.ResourceTemplate, policy.ActionUpdate)
937933
}))
938934
s.Run("DeleteOrganizationMember", s.Subtest(func(db database.Store, check *expects) {
@@ -3821,11 +3817,13 @@ func (s *MethodTestSuite) TestSystemFunctions() {
38213817
CreatedBy: user.ID,
38223818
})
38233819
preset, err := db.InsertPreset(ctx, database.InsertPresetParams{
3820+
ID: uuid.New(),
38243821
TemplateVersionID: templateVersion.ID,
38253822
Name: "test",
38263823
})
38273824
require.NoError(s.T(), err)
38283825
_, err = db.InsertPresetParameters(ctx, database.InsertPresetParametersParams{
3826+
ID: uuid.New(),
38293827
TemplateVersionPresetID: preset.ID,
38303828
Names: []string{"test"},
38313829
Values: []string{"test"},

coderd/database/dump.sql

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/migrations/000291_workspace_parameter_presets.up.sql

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
1-
-- TODO (sasswart): add IF NOT EXISTS and other clauses to make the migration more robust
21
CREATE TABLE template_version_presets
32
(
4-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
3+
id UUID PRIMARY KEY NOT NULL,
54
template_version_id UUID NOT NULL,
65
name TEXT NOT NULL,
76
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
8-
-- TODO (sasswart): Will auditing have any relevance to presets?
97
FOREIGN KEY (template_version_id) REFERENCES template_versions (id) ON DELETE CASCADE
108
);
119

1210
CREATE TABLE template_version_preset_parameters
1311
(
14-
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
12+
id UUID PRIMARY KEY NOT NULL,
1513
template_version_preset_id UUID NOT NULL,
1614
name TEXT NOT NULL,
17-
-- TODO (sasswart): would it be beneficial to allow presets to still offer a choice for values?
18-
-- This would allow an operator to avoid having to create many similar templates where only one or
19-
-- a few values are different.
2015
value TEXT NOT NULL,
2116
FOREIGN KEY (template_version_preset_id) REFERENCES template_version_presets (id) ON DELETE CASCADE
2217
);
@@ -28,9 +23,6 @@ ALTER TABLE workspace_builds
2823
ADD CONSTRAINT workspace_builds_template_version_preset_id_fkey
2924
FOREIGN KEY (template_version_preset_id)
3025
REFERENCES template_version_presets (id)
31-
-- TODO (sasswart): SET NULL might not be the best choice here. The rest of the hierarchy has ON DELETE CASCADE.
32-
-- We don't want CASCADE here, because we don't want to delete the workspace build if the preset is deleted.
33-
-- However, do we want to lose record of the preset id for a workspace build?
3426
ON DELETE SET NULL;
3527

3628
-- Recreate the view to include the new column.

coderd/database/queries.sql.go

Lines changed: 20 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/presets.sql

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
-- name: InsertPreset :one
22
INSERT INTO
3-
template_version_presets (template_version_id, name, created_at)
3+
template_version_presets (id, template_version_id, name, created_at)
44
VALUES
5-
(@template_version_id, @name, @created_at) RETURNING *;
5+
(@id, @template_version_id, @name, @created_at) RETURNING *;
66

77
-- name: InsertPresetParameters :many
88
INSERT INTO
9-
template_version_preset_parameters (template_version_preset_id, name, value)
9+
template_version_preset_parameters (id, template_version_preset_id, name, value)
1010
SELECT
11+
@id,
1112
@template_version_preset_id,
1213
unnest(@names :: TEXT[]),
1314
unnest(@values :: TEXT[])

coderd/provisionerdserver/provisionerdserver.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1830,6 +1830,7 @@ func InsertWorkspacePresetsAndParameters(ctx context.Context, logger slog.Logger
18301830
func InsertWorkspacePresetAndParameters(ctx context.Context, db database.Store, templateVersionID uuid.UUID, protoPreset *sdkproto.Preset, t time.Time) error {
18311831
err := db.InTx(func(tx database.Store) error {
18321832
dbPreset, err := tx.InsertPreset(ctx, database.InsertPresetParams{
1833+
ID: uuid.New(),
18331834
TemplateVersionID: templateVersionID,
18341835
Name: protoPreset.Name,
18351836
CreatedAt: t,
@@ -1845,6 +1846,7 @@ func InsertWorkspacePresetAndParameters(ctx context.Context, db database.Store,
18451846
presetParameterValues = append(presetParameterValues, parameter.Value)
18461847
}
18471848
_, err = tx.InsertPresetParameters(ctx, database.InsertPresetParametersParams{
1849+
ID: uuid.New(),
18481850
TemplateVersionPresetID: dbPreset.ID,
18491851
Names: presetParameterNames,
18501852
Values: presetParameterValues,

0 commit comments

Comments
 (0)