Skip to content

Commit e9bb650

Browse files
committed
update quotas
1 parent de06b53 commit e9bb650

File tree

10 files changed

+160
-25
lines changed

10 files changed

+160
-25
lines changed

coderd/database/dbauthz/dbauthz_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ func (s *MethodTestSuite) TestGroup() {
288288
s.Run("GetGroupMembers", s.Subtest(func(db database.Store, check *expects) {
289289
g := dbgen.Group(s.T(), db, database.Group{})
290290
_ = dbgen.GroupMember(s.T(), db, database.GroupMember{})
291-
check.Args(g.ID).Asserts(g, rbac.ActionRead)
291+
check.Args(database.GetGroupMembersParams{ID: g.ID, OrganizationID: g.OrganizationID}).Asserts(g, rbac.ActionRead)
292292
}))
293293
s.Run("InsertAllUsersGroup", s.Subtest(func(db database.Store, check *expects) {
294294
o := dbgen.Organization(s.T(), db, database.Organization{})

coderd/database/dbfake/dbfake.go

+14
Original file line numberDiff line numberDiff line change
@@ -1380,6 +1380,12 @@ func (q *FakeQuerier) GetGroupMembers(_ context.Context, arg database.GetGroupMe
13801380
q.mutex.RLock()
13811381
defer q.mutex.RUnlock()
13821382

1383+
if arg.ID == arg.OrganizationID {
1384+
cp := make([]database.User, len(q.users))
1385+
copy(cp, q.users)
1386+
return cp, nil
1387+
}
1388+
13831389
var members []database.GroupMember
13841390
for _, member := range q.groupMembers {
13851391
if member.GroupID == arg.ID {
@@ -1838,9 +1844,17 @@ func (q *FakeQuerier) GetQuotaAllowanceForUser(_ context.Context, userID uuid.UU
18381844
for _, group := range q.groups {
18391845
if group.ID == member.GroupID {
18401846
sum += int64(group.QuotaAllowance)
1847+
continue
18411848
}
18421849
}
18431850
}
1851+
// Grab the quota for the Everyone group.
1852+
for _, group := range q.groups {
1853+
if group.ID == group.OrganizationID {
1854+
sum += int64(group.QuotaAllowance)
1855+
break
1856+
}
1857+
}
18441858
return sum, nil
18451859
}
18461860

coderd/database/dbgen/dbgen_test.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,10 @@ func TestGenerator(t *testing.T) {
105105
exp := []database.User{u}
106106
dbgen.GroupMember(t, db, database.GroupMember{GroupID: g.ID, UserID: u.ID})
107107

108-
require.Equal(t, exp, must(db.GetGroupMembers(context.Background(), g.ID)))
108+
require.Equal(t, exp, must(db.GetGroupMembers(context.Background(), database.GetGroupMembersParams{
109+
ID: g.ID,
110+
OrganizationID: g.OrganizationID,
111+
})))
109112
})
110113

111114
t.Run("Organization", func(t *testing.T) {

coderd/database/queries.sql.go

+4-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/quotas.sql

+5-3
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
SELECT
33
coalesce(SUM(quota_allowance), 0)::BIGINT
44
FROM
5-
group_members gm
6-
JOIN groups g ON
5+
groups g
6+
LEFT JOIN group_members gm ON
77
g.id = gm.group_id
88
WHERE
9-
user_id = $1;
9+
user_id = $1
10+
OR
11+
g.id = g.organization_id;
1012

1113
-- name: GetQuotaConsumedForUser :one
1214
WITH latest_builds AS (

enterprise/coderd/groups.go

+8-6
Original file line numberDiff line numberDiff line change
@@ -113,18 +113,20 @@ func (api *API) patchGroup(rw http.ResponseWriter, r *http.Request) {
113113
req.Name = ""
114114
}
115115

116-
// TODO:
117-
// - Test no add/remove users to everyone group.
118-
// - Test no update everyone group name.
119-
// - Test no update everyone display name.
120-
121-
if group.ID == group.OrganizationID && (req.Name != "" || req.DisplayName != nil) {
116+
if group.ID == group.OrganizationID && req.Name != "" {
122117
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
123118
Message: fmt.Sprintf("Cannot rename the %q group!", database.AllUsersGroup),
124119
})
125120
return
126121
}
127122

123+
if group.ID == group.OrganizationID && (req.DisplayName != nil && *req.DisplayName != "") {
124+
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
125+
Message: fmt.Sprintf("Cannot update the Display Name for the %q group!", database.AllUsersGroup),
126+
})
127+
return
128+
}
129+
128130
if req.Name != "" && req.Name == database.AllUsersGroup {
129131
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
130132
Message: fmt.Sprintf("%q is a reserved group name!", database.AllUsersGroup),

enterprise/coderd/groups_test.go

+108-2
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ func TestPatchGroup(t *testing.T) {
399399
require.Equal(t, http.StatusBadRequest, cerr.StatusCode())
400400
})
401401

402-
t.Run("allUsers", func(t *testing.T) {
402+
t.Run("ReservedName", func(t *testing.T) {
403403
t.Parallel()
404404

405405
client, user := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
@@ -420,6 +420,108 @@ func TestPatchGroup(t *testing.T) {
420420
cerr, ok := codersdk.AsError(err)
421421
require.True(t, ok)
422422
require.Equal(t, http.StatusBadRequest, cerr.StatusCode())
423+
424+
})
425+
426+
t.Run("EveryoneGroup", func(t *testing.T) {
427+
t.Parallel()
428+
t.Run("NoUpdateName", func(t *testing.T) {
429+
t.Parallel()
430+
431+
client, user := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
432+
Features: license.Features{
433+
codersdk.FeatureTemplateRBAC: 1,
434+
},
435+
}})
436+
ctx := testutil.Context(t, testutil.WaitLong)
437+
_, err := client.PatchGroup(ctx, user.OrganizationID, codersdk.PatchGroupRequest{
438+
Name: "hi",
439+
})
440+
require.Error(t, err)
441+
cerr, ok := codersdk.AsError(err)
442+
require.True(t, ok)
443+
require.Equal(t, http.StatusBadRequest, cerr.StatusCode())
444+
})
445+
446+
t.Run("NoUpdateDisplayName", func(t *testing.T) {
447+
t.Parallel()
448+
449+
client, user := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
450+
Features: license.Features{
451+
codersdk.FeatureTemplateRBAC: 1,
452+
},
453+
}})
454+
ctx := testutil.Context(t, testutil.WaitLong)
455+
_, err := client.PatchGroup(ctx, user.OrganizationID, codersdk.PatchGroupRequest{
456+
DisplayName: ptr.Ref("hi"),
457+
})
458+
require.Error(t, err)
459+
cerr, ok := codersdk.AsError(err)
460+
require.True(t, ok)
461+
require.Equal(t, http.StatusBadRequest, cerr.StatusCode())
462+
})
463+
464+
t.Run("NoAddUsers", func(t *testing.T) {
465+
t.Parallel()
466+
467+
client, user := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
468+
Features: license.Features{
469+
codersdk.FeatureTemplateRBAC: 1,
470+
},
471+
}})
472+
_, user2 := coderdtest.CreateAnotherUser(t, client, user.OrganizationID)
473+
474+
ctx := testutil.Context(t, testutil.WaitLong)
475+
_, err := client.PatchGroup(ctx, user.OrganizationID, codersdk.PatchGroupRequest{
476+
AddUsers: []string{user2.ID.String()},
477+
})
478+
require.Error(t, err)
479+
cerr, ok := codersdk.AsError(err)
480+
require.True(t, ok)
481+
require.Equal(t, http.StatusBadRequest, cerr.StatusCode())
482+
})
483+
484+
t.Run("NoRmUsers", func(t *testing.T) {
485+
t.Parallel()
486+
487+
client, user := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
488+
Features: license.Features{
489+
codersdk.FeatureTemplateRBAC: 1,
490+
},
491+
}})
492+
493+
ctx := testutil.Context(t, testutil.WaitLong)
494+
_, err := client.PatchGroup(ctx, user.OrganizationID, codersdk.PatchGroupRequest{
495+
RemoveUsers: []string{user.UserID.String()},
496+
})
497+
require.Error(t, err)
498+
cerr, ok := codersdk.AsError(err)
499+
require.True(t, ok)
500+
require.Equal(t, http.StatusBadRequest, cerr.StatusCode())
501+
})
502+
503+
t.Run("UpdateQuota", func(t *testing.T) {
504+
t.Parallel()
505+
506+
client, user := coderdenttest.New(t, &coderdenttest.Options{LicenseOptions: &coderdenttest.LicenseOptions{
507+
Features: license.Features{
508+
codersdk.FeatureTemplateRBAC: 1,
509+
},
510+
}})
511+
512+
ctx := testutil.Context(t, testutil.WaitLong)
513+
group, err := client.Group(ctx, user.OrganizationID)
514+
require.NoError(t, err)
515+
516+
require.Equal(t, 0, group.QuotaAllowance)
517+
518+
expectedQuota := 123
519+
group, err = client.PatchGroup(ctx, user.OrganizationID, codersdk.PatchGroupRequest{
520+
QuotaAllowance: ptr.Ref(expectedQuota),
521+
})
522+
require.NoError(t, err)
523+
require.Equal(t, expectedQuota, group.QuotaAllowance)
524+
})
423525
})
424526
}
425527

@@ -591,13 +693,17 @@ func TestGroup(t *testing.T) {
591693
codersdk.FeatureTemplateRBAC: 1,
592694
},
593695
}})
696+
_, user1 := coderdtest.CreateAnotherUser(t, client, user.OrganizationID)
697+
_, user2 := coderdtest.CreateAnotherUser(t, client, user.OrganizationID)
698+
594699
ctx := testutil.Context(t, testutil.WaitLong)
595700
// The 'Everyone' group always has an ID that matches the organization ID.
596701
group, err := client.Group(ctx, user.OrganizationID)
597702
require.NoError(t, err)
598-
require.Len(t, group.Members, 0)
703+
require.Len(t, group.Members, 3)
599704
require.Equal(t, "Everyone", group.Name)
600705
require.Equal(t, user.OrganizationID, group.OrganizationID)
706+
require.Contains(t, group.Members, user1, user2)
601707
})
602708
}
603709

enterprise/coderd/templates_test.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -373,8 +373,7 @@ func TestTemplateACL(t *testing.T) {
373373
require.NoError(t, err)
374374

375375
require.Len(t, acl.Groups, 1)
376-
// We don't return members for the 'Everyone' group.
377-
require.Len(t, acl.Groups[0].Members, 0)
376+
require.Len(t, acl.Groups[0].Members, 2)
378377
require.Len(t, acl.Users, 0)
379378
})
380379

enterprise/coderd/workspacequota_test.go

+15-7
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/stretchr/testify/require"
1111

1212
"github.com/coder/coder/coderd/coderdtest"
13+
"github.com/coder/coder/coderd/util/ptr"
1314
"github.com/coder/coder/codersdk"
1415
"github.com/coder/coder/enterprise/coderd/coderdenttest"
1516
"github.com/coder/coder/enterprise/coderd/license"
@@ -53,7 +54,14 @@ func TestWorkspaceQuota(t *testing.T) {
5354

5455
verifyQuota(ctx, t, client, 0, 0)
5556

56-
// Add user to two groups, granting them a total budget of 3.
57+
// Patch the 'Everyone' group to verify its quota allowance is being accounted for.
58+
_, err := client.PatchGroup(ctx, user.OrganizationID, codersdk.PatchGroupRequest{
59+
QuotaAllowance: ptr.Ref(1),
60+
})
61+
require.NoError(t, err)
62+
verifyQuota(ctx, t, client, 0, 1)
63+
64+
// Add user to two groups, granting them a total budget of 4.
5765
group1, err := client.CreateGroup(ctx, user.OrganizationID, codersdk.CreateGroupRequest{
5866
Name: "test-1",
5967
QuotaAllowance: 1,
@@ -76,7 +84,7 @@ func TestWorkspaceQuota(t *testing.T) {
7684
})
7785
require.NoError(t, err)
7886

79-
verifyQuota(ctx, t, client, 0, 3)
87+
verifyQuota(ctx, t, client, 0, 4)
8088

8189
authToken := uuid.NewString()
8290
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
@@ -105,7 +113,7 @@ func TestWorkspaceQuota(t *testing.T) {
105113

106114
// Spin up three workspaces fine
107115
var wg sync.WaitGroup
108-
for i := 0; i < 3; i++ {
116+
for i := 0; i < 4; i++ {
109117
wg.Add(1)
110118
go func() {
111119
defer wg.Done()
@@ -115,14 +123,14 @@ func TestWorkspaceQuota(t *testing.T) {
115123
}()
116124
}
117125
wg.Wait()
118-
verifyQuota(ctx, t, client, 3, 3)
126+
verifyQuota(ctx, t, client, 4, 4)
119127

120128
// Next one must fail
121129
workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
122130
build := coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
123131

124132
// Consumed shouldn't bump
125-
verifyQuota(ctx, t, client, 3, 3)
133+
verifyQuota(ctx, t, client, 4, 4)
126134
require.Equal(t, codersdk.WorkspaceStatusFailed, build.Status)
127135
require.Contains(t, build.Job.Error, "quota")
128136

@@ -138,15 +146,15 @@ func TestWorkspaceQuota(t *testing.T) {
138146
})
139147
require.NoError(t, err)
140148
coderdtest.AwaitWorkspaceBuildJob(t, client, build.ID)
141-
verifyQuota(ctx, t, client, 2, 3)
149+
verifyQuota(ctx, t, client, 3, 4)
142150
break
143151
}
144152

145153
// Next one should now succeed
146154
workspace = coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
147155
build = coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
148156

149-
verifyQuota(ctx, t, client, 3, 3)
157+
verifyQuota(ctx, t, client, 4, 4)
150158
require.Equal(t, codersdk.WorkspaceStatusRunning, build.Status)
151159
})
152160
}

site/src/pages/GroupsPage/SettingsGroupPageView.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { useTranslation } from "react-i18next"
1212
import { getFormHelpers, nameValidator, onChangeTrimmed } from "utils/formUtils"
1313
import * as Yup from "yup"
1414
import { Stack } from "components/Stack/Stack"
15-
import { GroupRemove } from "@mui/icons-material"
1615

1716
type FormData = {
1817
name: string

0 commit comments

Comments
 (0)