Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitcf10d98

Browse files
authored
fix: improve error message when deleting organization with resources (#17049)
Closes[coder/internal#477](coder/internal#477)![Screenshot 2025-03-21 at 11 2557 AM](https://github.com/user-attachments/assets/50cc03e9-395d-4fc7-8882-18cb66b1fac9)I'm solving this issue in two parts:1. Updated the postgres function so that it doesn't omit 0 values in theerror2. Created a new query to fetch the number of resources associated withan organization and using that information to provider a cleaner errormessage to the frontend> **_NOTE:_** SQL is not my strong suit, and the code was created withthe help of AI. So I'd take extra time looking over what I wrote there
1 parent2c53f7a commitcf10d98

13 files changed

+416
-22
lines changed

‎coderd/database/dbauthz/dbauthz.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1989,6 +1989,35 @@ func (q *querier) GetOrganizationIDsByMemberIDs(ctx context.Context, ids []uuid.
19891989
returnfetchWithPostFilter(q.auth,policy.ActionRead,q.db.GetOrganizationIDsByMemberIDs)(ctx,ids)
19901990
}
19911991

1992+
func (q*querier)GetOrganizationResourceCountByID(ctx context.Context,organizationID uuid.UUID) (database.GetOrganizationResourceCountByIDRow,error) {
1993+
// Can read org members
1994+
iferr:=q.authorizeContext(ctx,policy.ActionRead,rbac.ResourceOrganizationMember.InOrg(organizationID));err!=nil {
1995+
return database.GetOrganizationResourceCountByIDRow{},err
1996+
}
1997+
1998+
// Can read org workspaces
1999+
iferr:=q.authorizeContext(ctx,policy.ActionRead,rbac.ResourceWorkspace.InOrg(organizationID));err!=nil {
2000+
return database.GetOrganizationResourceCountByIDRow{},err
2001+
}
2002+
2003+
// Can read org groups
2004+
iferr:=q.authorizeContext(ctx,policy.ActionRead,rbac.ResourceGroup.InOrg(organizationID));err!=nil {
2005+
return database.GetOrganizationResourceCountByIDRow{},err
2006+
}
2007+
2008+
// Can read org templates
2009+
iferr:=q.authorizeContext(ctx,policy.ActionRead,rbac.ResourceTemplate.InOrg(organizationID));err!=nil {
2010+
return database.GetOrganizationResourceCountByIDRow{},err
2011+
}
2012+
2013+
// Can read org provisioner daemons
2014+
iferr:=q.authorizeContext(ctx,policy.ActionRead,rbac.ResourceProvisionerDaemon.InOrg(organizationID));err!=nil {
2015+
return database.GetOrganizationResourceCountByIDRow{},err
2016+
}
2017+
2018+
returnq.db.GetOrganizationResourceCountByID(ctx,organizationID)
2019+
}
2020+
19922021
func (q*querier)GetOrganizations(ctx context.Context,args database.GetOrganizationsParams) ([]database.Organization,error) {
19932022
fetch:=func(ctx context.Context,_interface{}) ([]database.Organization,error) {
19942023
returnq.db.GetOrganizations(ctx,args)

‎coderd/database/dbauthz/dbauthz_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -815,6 +815,39 @@ func (s *MethodTestSuite) TestOrganization() {
815815
o:=dbgen.Organization(s.T(),db, database.Organization{})
816816
check.Args(o.ID).Asserts(o,policy.ActionRead).Returns(o)
817817
}))
818+
s.Run("GetOrganizationResourceCountByID",s.Subtest(func(db database.Store,check*expects) {
819+
u:=dbgen.User(s.T(),db, database.User{})
820+
o:=dbgen.Organization(s.T(),db, database.Organization{})
821+
822+
t:=dbgen.Template(s.T(),db, database.Template{
823+
CreatedBy:u.ID,
824+
OrganizationID:o.ID,
825+
})
826+
dbgen.Workspace(s.T(),db, database.WorkspaceTable{
827+
OrganizationID:o.ID,
828+
OwnerID:u.ID,
829+
TemplateID:t.ID,
830+
})
831+
dbgen.Group(s.T(),db, database.Group{OrganizationID:o.ID})
832+
dbgen.OrganizationMember(s.T(),db, database.OrganizationMember{
833+
OrganizationID:o.ID,
834+
UserID:u.ID,
835+
})
836+
837+
check.Args(o.ID).Asserts(
838+
rbac.ResourceOrganizationMember.InOrg(o.ID),policy.ActionRead,
839+
rbac.ResourceWorkspace.InOrg(o.ID),policy.ActionRead,
840+
rbac.ResourceGroup.InOrg(o.ID),policy.ActionRead,
841+
rbac.ResourceTemplate.InOrg(o.ID),policy.ActionRead,
842+
rbac.ResourceProvisionerDaemon.InOrg(o.ID),policy.ActionRead,
843+
).Returns(database.GetOrganizationResourceCountByIDRow{
844+
WorkspaceCount:1,
845+
GroupCount:1,
846+
TemplateCount:1,
847+
MemberCount:1,
848+
ProvisionerKeyCount:0,
849+
})
850+
}))
818851
s.Run("GetDefaultOrganization",s.Subtest(func(db database.Store,check*expects) {
819852
o,_:=db.GetDefaultOrganization(context.Background())
820853
check.Args().Asserts(o,policy.ActionRead).Returns(o)

‎coderd/database/dbmem/dbmem.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4008,6 +4008,54 @@ func (q *FakeQuerier) GetOrganizationIDsByMemberIDs(_ context.Context, ids []uui
40084008
returngetOrganizationIDsByMemberIDRows,nil
40094009
}
40104010

4011+
func (q*FakeQuerier)GetOrganizationResourceCountByID(_ context.Context,organizationID uuid.UUID) (database.GetOrganizationResourceCountByIDRow,error) {
4012+
q.mutex.RLock()
4013+
deferq.mutex.RUnlock()
4014+
4015+
workspacesCount:=0
4016+
for_,workspace:=rangeq.workspaces {
4017+
ifworkspace.OrganizationID==organizationID {
4018+
workspacesCount++
4019+
}
4020+
}
4021+
4022+
groupsCount:=0
4023+
for_,group:=rangeq.groups {
4024+
ifgroup.OrganizationID==organizationID {
4025+
groupsCount++
4026+
}
4027+
}
4028+
4029+
templatesCount:=0
4030+
for_,template:=rangeq.templates {
4031+
iftemplate.OrganizationID==organizationID {
4032+
templatesCount++
4033+
}
4034+
}
4035+
4036+
organizationMembersCount:=0
4037+
for_,organizationMember:=rangeq.organizationMembers {
4038+
iforganizationMember.OrganizationID==organizationID {
4039+
organizationMembersCount++
4040+
}
4041+
}
4042+
4043+
provKeyCount:=0
4044+
for_,provKey:=rangeq.provisionerKeys {
4045+
ifprovKey.OrganizationID==organizationID {
4046+
provKeyCount++
4047+
}
4048+
}
4049+
4050+
return database.GetOrganizationResourceCountByIDRow{
4051+
WorkspaceCount:int64(workspacesCount),
4052+
GroupCount:int64(groupsCount),
4053+
TemplateCount:int64(templatesCount),
4054+
MemberCount:int64(organizationMembersCount),
4055+
ProvisionerKeyCount:int64(provKeyCount),
4056+
},nil
4057+
}
4058+
40114059
func (q*FakeQuerier)GetOrganizations(_ context.Context,args database.GetOrganizationsParams) ([]database.Organization,error) {
40124060
q.mutex.RLock()
40134061
deferq.mutex.RUnlock()

‎coderd/database/dbmetrics/querymetrics.go

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

‎coderd/database/dbmock/dbmock.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/database/dump.sql

Lines changed: 38 additions & 19 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
-- Drop trigger that uses this function
2+
DROPTRIGGER IF EXISTS protect_deleting_organizationsON organizations;
3+
4+
-- Revert the function to its original implementation
5+
CREATE OR REPLACEFUNCTIONprotect_deleting_organizations()
6+
RETURNS TRIGGERAS
7+
$$
8+
DECLARE
9+
workspace_countint;
10+
template_countint;
11+
group_countint;
12+
member_countint;
13+
provisioner_keys_countint;
14+
BEGIN
15+
workspace_count := (
16+
SELECTcount(*)as countFROM workspaces
17+
WHERE
18+
workspaces.organization_id=OLD.id
19+
ANDworkspaces.deleted= false
20+
);
21+
22+
template_count := (
23+
SELECTcount(*)as countFROM templates
24+
WHERE
25+
templates.organization_id=OLD.id
26+
ANDtemplates.deleted= false
27+
);
28+
29+
group_count := (
30+
SELECTcount(*)as countFROM groups
31+
WHERE
32+
groups.organization_id=OLD.id
33+
);
34+
35+
member_count := (
36+
SELECTcount(*)as countFROM organization_members
37+
WHERE
38+
organization_members.organization_id=OLD.id
39+
);
40+
41+
provisioner_keys_count := (
42+
Selectcount(*)as countFROM provisioner_keys
43+
WHERE
44+
provisioner_keys.organization_id=OLD.id
45+
);
46+
47+
-- Fail the deletion if one of the following:
48+
-- * the organization has 1 or more workspaces
49+
-- * the organization has 1 or more templates
50+
-- * the organization has 1 or more groups other than "Everyone" group
51+
-- * the organization has 1 or more members other than the organization owner
52+
-- * the organization has 1 or more provisioner keys
53+
54+
IF (workspace_count+ template_count+ provisioner_keys_count)>0 THEN
55+
RAISE EXCEPTION'cannot delete organization: organization has % workspaces, % templates, and % provisioner keys that must be deleted first', workspace_count, template_count, provisioner_keys_count;
56+
END IF;
57+
58+
IF (group_count)>1 THEN
59+
RAISE EXCEPTION'cannot delete organization: organization has % groups that must be deleted first', group_count-1;
60+
END IF;
61+
62+
-- Allow 1 member to exist, because you cannot remove yourself. You can
63+
-- remove everyone else. Ideally, we only omit the member that matches
64+
-- the user_id of the caller, however in a trigger, the caller is unknown.
65+
IF (member_count)>1 THEN
66+
RAISE EXCEPTION'cannot delete organization: organization has % members that must be deleted first', member_count-1;
67+
END IF;
68+
69+
RETURN NEW;
70+
END;
71+
$$ LANGUAGE plpgsql;
72+
73+
-- Re-create trigger that uses this function
74+
CREATETRIGGERprotect_deleting_organizations
75+
BEFOREDELETEON organizations
76+
FOR EACH ROW
77+
EXECUTE FUNCTION protect_deleting_organizations();

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp