Skip to content

Commit 3fff28a

Browse files
authored
Merge branch 'main' into jakehwll/mui-cssnuke-components
2 parents 4d24a2f + e4a06f8 commit 3fff28a

File tree

12 files changed

+840
-486
lines changed

12 files changed

+840
-486
lines changed

coderd/httpmw/prometheus.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,6 @@ func getRoutePattern(r *http.Request) string {
106106
return ""
107107
}
108108

109-
if pattern := rctx.RoutePattern(); pattern != "" {
110-
// Pattern is already available
111-
return pattern
112-
}
113-
114109
routePath := r.URL.Path
115110
if r.URL.RawPath != "" {
116111
routePath = r.URL.RawPath

coderd/httpmw/prometheus_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ package httpmw_test
22

33
import (
44
"context"
5+
"fmt"
56
"net/http"
67
"net/http/httptest"
78
"testing"
89

910
"github.com/go-chi/chi/v5"
11+
"github.com/google/uuid"
1012
"github.com/prometheus/client_golang/prometheus"
1113
cm "github.com/prometheus/client_model/go"
1214
"github.com/stretchr/testify/assert"
@@ -164,6 +166,42 @@ func TestPrometheus(t *testing.T) {
164166
require.Equal(t, "UNKNOWN", reqProcessed["path"])
165167
require.Equal(t, "GET", reqProcessed["method"])
166168
})
169+
170+
t.Run("Subrouter", func(t *testing.T) {
171+
t.Parallel()
172+
reg := prometheus.NewRegistry()
173+
promMW := httpmw.Prometheus(reg)
174+
175+
r := chi.NewRouter()
176+
r.Use(promMW)
177+
r.Get("/api/v2/workspaceagents/{workspaceagent}/pty", func(w http.ResponseWriter, r *http.Request) {})
178+
179+
// Mount under a root router like wsproxy does.
180+
rootRouter := chi.NewRouter()
181+
rootRouter.Get("/latency-check", func(w http.ResponseWriter, r *http.Request) {})
182+
rootRouter.Mount("/", r)
183+
184+
agentID := uuid.UUID{1}
185+
req := httptest.NewRequest("GET", fmt.Sprintf("/api/v2/workspaceagents/%s/pty", agentID.String()), nil)
186+
187+
sw := &tracing.StatusWriter{ResponseWriter: httptest.NewRecorder()}
188+
rootRouter.ServeHTTP(sw, req)
189+
190+
metrics, err := reg.Gather()
191+
require.NoError(t, err)
192+
require.Greater(t, len(metrics), 0)
193+
metricLabels := getMetricLabels(metrics)
194+
195+
reqProcessed, ok := metricLabels["coderd_api_requests_processed_total"]
196+
require.True(t, ok, "coderd_api_requests_processed_total metric not found")
197+
require.Equal(t, "/api/v2/workspaceagents/{workspaceagent}/pty", reqProcessed["path"])
198+
require.Equal(t, "GET", reqProcessed["method"])
199+
200+
concurrentRequests, ok := metricLabels["coderd_api_concurrent_requests"]
201+
require.True(t, ok, "coderd_api_concurrent_requests metric not found")
202+
require.Equal(t, "/api/v2/workspaceagents/{workspaceagent}/pty", concurrentRequests["path"])
203+
require.Equal(t, "GET", concurrentRequests["method"])
204+
})
167205
}
168206

169207
func getMetricLabels(metrics []*cm.MetricFamily) map[string]map[string]string {

enterprise/coderd/workspacebuilds_test.go

Lines changed: 147 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -16,106 +16,143 @@ import (
1616

1717
func TestWorkspaceBuild(t *testing.T) {
1818
t.Parallel()
19-
t.Run("TemplateRequiresActiveVersion", func(t *testing.T) {
20-
t.Parallel()
2119

22-
ctx := testutil.Context(t, testutil.WaitMedium)
23-
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
24-
Options: &coderdtest.Options{
25-
IncludeProvisionerDaemon: true,
26-
},
27-
LicenseOptions: &coderdenttest.LicenseOptions{
28-
Features: license.Features{
29-
codersdk.FeatureAccessControl: 1,
30-
codersdk.FeatureTemplateRBAC: 1,
31-
codersdk.FeatureAdvancedTemplateScheduling: 1,
32-
},
20+
// Only use this context for setup. Use a separate context for subtests!
21+
setupCtx := testutil.Context(t, testutil.WaitMedium)
22+
ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{
23+
Options: &coderdtest.Options{
24+
IncludeProvisionerDaemon: true,
25+
},
26+
LicenseOptions: &coderdenttest.LicenseOptions{
27+
Features: license.Features{
28+
codersdk.FeatureAccessControl: 1,
29+
codersdk.FeatureTemplateRBAC: 1,
30+
codersdk.FeatureAdvancedTemplateScheduling: 1,
3331
},
34-
})
32+
},
33+
})
3534

36-
// Create an initial version.
37-
oldVersion := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil)
38-
// Create a template that mandates the promoted version.
39-
// This should be enforced for everyone except template admins.
40-
template := coderdtest.CreateTemplate(t, ownerClient, owner.OrganizationID, oldVersion.ID)
41-
coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, oldVersion.ID)
42-
require.Equal(t, oldVersion.ID, template.ActiveVersionID)
43-
template = coderdtest.UpdateTemplateMeta(t, ownerClient, template.ID, codersdk.UpdateTemplateMeta{
44-
RequireActiveVersion: true,
45-
})
46-
require.True(t, template.RequireActiveVersion)
35+
// For this test we create two templates:
36+
// tplA will be used to test creation of new workspaces.
37+
// tplB will be used to test builds on existing workspaces.
38+
// This is done to enable parallelization of the sub-tests without them interfering with each other.
39+
// Both templates mandate the promoted version.
40+
// This should be enforced for everyone except template admins.
41+
tplAv1 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil)
42+
tplA := coderdtest.CreateTemplate(t, ownerClient, owner.OrganizationID, tplAv1.ID)
43+
coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, tplAv1.ID)
44+
require.Equal(t, tplAv1.ID, tplA.ActiveVersionID)
45+
tplA = coderdtest.UpdateTemplateMeta(t, ownerClient, tplA.ID, codersdk.UpdateTemplateMeta{
46+
RequireActiveVersion: true,
47+
})
48+
require.True(t, tplA.RequireActiveVersion)
49+
tplAv2 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil, func(ctvr *codersdk.CreateTemplateVersionRequest) {
50+
ctvr.TemplateID = tplA.ID
51+
})
52+
coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, tplAv2.ID)
53+
coderdtest.UpdateActiveTemplateVersion(t, ownerClient, tplA.ID, tplAv2.ID)
4754

48-
// Create a new version that we will promote.
49-
activeVersion := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil, func(ctvr *codersdk.CreateTemplateVersionRequest) {
50-
ctvr.TemplateID = template.ID
51-
})
52-
coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, activeVersion.ID)
53-
coderdtest.UpdateActiveTemplateVersion(t, ownerClient, template.ID, activeVersion.ID)
55+
tplBv1 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil)
56+
tplB := coderdtest.CreateTemplate(t, ownerClient, owner.OrganizationID, tplBv1.ID)
57+
coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, tplBv1.ID)
58+
require.Equal(t, tplBv1.ID, tplB.ActiveVersionID)
59+
tplB = coderdtest.UpdateTemplateMeta(t, ownerClient, tplB.ID, codersdk.UpdateTemplateMeta{
60+
RequireActiveVersion: true,
61+
})
62+
require.True(t, tplB.RequireActiveVersion)
5463

55-
templateAdminClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin())
56-
templateACLAdminClient, templateACLAdmin := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID)
57-
templateGroupACLAdminClient, templateGroupACLAdmin := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID)
58-
memberClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID)
64+
templateAdminClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin())
65+
templateACLAdminClient, templateACLAdmin := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID)
66+
templateGroupACLAdminClient, templateGroupACLAdmin := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID)
67+
memberClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID)
5968

60-
// Create a group so we can also test group template admin ownership.
61-
// Add the user who gains template admin via group membership.
62-
group := coderdtest.CreateGroup(t, ownerClient, owner.OrganizationID, "test", templateGroupACLAdmin)
69+
// Create a group so we can also test group template admin ownership.
70+
// Add the user who gains template admin via group membership.
71+
group := coderdtest.CreateGroup(t, ownerClient, owner.OrganizationID, "test", templateGroupACLAdmin)
6372

64-
// Update the template for both users and groups.
65-
//nolint:gocritic // test setup
66-
err := ownerClient.UpdateTemplateACL(ctx, template.ID, codersdk.UpdateTemplateACL{
73+
// Update the template for both users and groups.
74+
//nolint:gocritic // test setup
75+
for _, tpl := range []codersdk.Template{tplA, tplB} {
76+
err := ownerClient.UpdateTemplateACL(setupCtx, tpl.ID, codersdk.UpdateTemplateACL{
6777
UserPerms: map[string]codersdk.TemplateRole{
6878
templateACLAdmin.ID.String(): codersdk.TemplateRoleAdmin,
6979
},
7080
GroupPerms: map[string]codersdk.TemplateRole{
7181
group.ID.String(): codersdk.TemplateRoleAdmin,
7282
},
7383
})
74-
require.NoError(t, err)
84+
require.NoError(t, err, "updating template ACL for template %q", tpl.ID)
85+
}
7586

76-
type testcase struct {
77-
Name string
78-
Client *codersdk.Client
79-
ExpectedStatusCode int
80-
}
87+
type testcase struct {
88+
Name string
89+
Client *codersdk.Client
90+
ExpectedStatusCode int
91+
}
8192

82-
cases := []testcase{
83-
{
84-
Name: "OwnerOK",
85-
Client: ownerClient,
86-
ExpectedStatusCode: http.StatusOK,
87-
},
88-
{
89-
Name: "TemplateAdminOK",
90-
Client: templateAdminClient,
91-
ExpectedStatusCode: http.StatusOK,
92-
},
93-
{
94-
Name: "TemplateACLAdminOK",
95-
Client: templateACLAdminClient,
96-
ExpectedStatusCode: http.StatusOK,
97-
},
98-
{
99-
Name: "TemplateGroupACLAdminOK",
100-
Client: templateGroupACLAdminClient,
101-
ExpectedStatusCode: http.StatusOK,
102-
},
103-
{
104-
Name: "MemberFails",
105-
Client: memberClient,
106-
ExpectedStatusCode: http.StatusForbidden,
107-
},
108-
}
93+
cases := []testcase{
94+
{
95+
Name: "OwnerOK",
96+
Client: ownerClient,
97+
ExpectedStatusCode: http.StatusOK,
98+
},
99+
{
100+
Name: "TemplateAdminOK",
101+
Client: templateAdminClient,
102+
ExpectedStatusCode: http.StatusOK,
103+
},
104+
{
105+
Name: "TemplateACLAdminOK",
106+
Client: templateACLAdminClient,
107+
ExpectedStatusCode: http.StatusOK,
108+
},
109+
{
110+
Name: "TemplateGroupACLAdminOK",
111+
Client: templateGroupACLAdminClient,
112+
ExpectedStatusCode: http.StatusOK,
113+
},
114+
{
115+
Name: "MemberFailsToCreate",
116+
Client: memberClient,
117+
ExpectedStatusCode: http.StatusForbidden,
118+
},
119+
}
120+
121+
// Create pre-existing workspaces for each of the test cases.
122+
var extantWorkspaces []codersdk.Workspace
123+
for _, c := range cases {
124+
extantWs, err := c.Client.CreateUserWorkspace(setupCtx, codersdk.Me, codersdk.CreateWorkspaceRequest{
125+
TemplateVersionID: tplB.ActiveVersionID,
126+
Name: testutil.GetRandomNameHyphenated(t),
127+
AutomaticUpdates: codersdk.AutomaticUpdatesNever,
128+
})
129+
require.NoError(t, err, "setup workspace for case %q", c.Name)
130+
coderdtest.AwaitWorkspaceBuildJobCompleted(t, c.Client, extantWs.LatestBuild.ID)
131+
extantWorkspaces = append(extantWorkspaces, extantWs)
132+
}
133+
134+
// Create a new version of template B and promote it to be the active version.
135+
tplBv2 := coderdtest.CreateTemplateVersion(t, ownerClient, owner.OrganizationID, nil, func(ctvr *codersdk.CreateTemplateVersionRequest) {
136+
ctvr.TemplateID = tplB.ID
137+
})
138+
coderdtest.AwaitTemplateVersionJobCompleted(t, ownerClient, tplBv2.ID)
139+
coderdtest.UpdateActiveTemplateVersion(t, ownerClient, tplB.ID, tplBv2.ID)
140+
141+
t.Run("NewWorkspace", func(t *testing.T) {
142+
t.Parallel()
109143

110144
for _, c := range cases {
111145
t.Run(c.Name, func(t *testing.T) {
112-
_, err = c.Client.CreateUserWorkspace(ctx, codersdk.Me, codersdk.CreateWorkspaceRequest{
113-
TemplateVersionID: oldVersion.ID,
114-
Name: "abc123",
146+
t.Parallel()
147+
ctx := testutil.Context(t, testutil.WaitMedium)
148+
ws, err := c.Client.CreateUserWorkspace(ctx, codersdk.Me, codersdk.CreateWorkspaceRequest{
149+
TemplateVersionID: tplAv1.ID,
150+
Name: testutil.GetRandomNameHyphenated(t),
115151
AutomaticUpdates: codersdk.AutomaticUpdatesNever,
116152
})
117153
if c.ExpectedStatusCode == http.StatusOK {
118154
require.NoError(t, err)
155+
require.Equal(t, tplAv1.ID, ws.LatestBuild.TemplateVersionID, "workspace did not use expected version for case %q", c.Name)
119156
} else {
120157
require.Error(t, err)
121158
cerr, ok := codersdk.AsError(err)
@@ -125,4 +162,37 @@ func TestWorkspaceBuild(t *testing.T) {
125162
})
126163
}
127164
})
165+
166+
t.Run("ExistingWorkspace", func(t *testing.T) {
167+
t.Parallel()
168+
169+
for idx, c := range cases {
170+
t.Run(c.Name, func(t *testing.T) {
171+
t.Parallel()
172+
ctx := testutil.Context(t, testutil.WaitMedium)
173+
// Stopping the workspace must always succeed.
174+
wb, err := c.Client.CreateWorkspaceBuild(ctx, extantWorkspaces[idx].ID, codersdk.CreateWorkspaceBuildRequest{
175+
Transition: codersdk.WorkspaceTransitionStop,
176+
})
177+
require.NoError(t, err, "stopping workspace for case %q", c.Name)
178+
coderdtest.AwaitWorkspaceBuildJobCompleted(t, c.Client, wb.ID)
179+
180+
// Attempt to start the workspace with the given version.
181+
wb, err = c.Client.CreateWorkspaceBuild(ctx, extantWorkspaces[idx].ID, codersdk.CreateWorkspaceBuildRequest{
182+
Transition: codersdk.WorkspaceTransitionStart,
183+
TemplateVersionID: tplBv1.ID,
184+
})
185+
if c.ExpectedStatusCode == http.StatusOK {
186+
require.NoError(t, err, "starting workspace for case %q", c.Name)
187+
coderdtest.AwaitWorkspaceBuildJobCompleted(t, c.Client, wb.ID)
188+
require.Equal(t, tplBv1.ID, wb.TemplateVersionID, "workspace did not use expected version for case %q", c.Name)
189+
} else {
190+
require.Error(t, err, "starting workspace for case %q", c.Name)
191+
cerr, ok := codersdk.AsError(err)
192+
require.True(t, ok)
193+
require.Equal(t, c.ExpectedStatusCode, cerr.StatusCode())
194+
}
195+
})
196+
}
197+
})
128198
}

site/src/modules/groups.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
1-
import type { Group, ReducedUser, User } from "api/typesGenerated";
1+
import type {
2+
Group,
3+
ReducedUser,
4+
User,
5+
WorkspaceUser,
6+
} from "api/typesGenerated";
27

3-
type UserOrGroupAutocompleteValue = User | ReducedUser | Group | null;
8+
/**
9+
* Union of all user-like types that can be distinguished from Group.
10+
*/
11+
type UserLike = User | ReducedUser | WorkspaceUser;
412

513
/**
614
* Type guard to check if the value is a Group.
715
* Groups have a "members" property that users don't have.
816
*/
9-
export const isGroup = (
10-
value: UserOrGroupAutocompleteValue,
11-
): value is Group => {
12-
return value !== null && typeof value === "object" && "members" in value;
17+
export const isGroup = (value: UserLike | Group): value is Group => {
18+
return "members" in value;
1319
};
1420

1521
/**

0 commit comments

Comments
 (0)