@@ -2,7 +2,6 @@ package prebuilds
22
33import (
44"context"
5- "database/sql"
65"errors"
76
87"github.com/google/uuid"
@@ -32,103 +31,78 @@ func NewStoreMembershipReconciler(store database.Store, clock quartz.Clock) Stor
3231}
3332}
3433
35- // ReconcileAll compares the current organization and group memberships of a user to the memberships required
36- // in order to create prebuilt workspaces. If the user in question is not yet a member of an organization that
37- // needs prebuilt workspaces, ReconcileAll will create the membership required.
34+ // ReconcileAll ensures the prebuilds system user has the necessary memberships to create prebuilt workspaces.
35+ // For each organization with prebuilds configured, it ensures:
36+ // * The user is a member of the organization
37+ // * A group exists with quota 0
38+ // * The user is a member of that group
3839//
39- // To facilitate quota management, ReconcileAll will ensure:
40- // * the existence of a group (defined by PrebuiltWorkspacesGroupName) in each organization that needs prebuilt workspaces
41- // * that the prebuilds system user belongs to the group in each organization that needs prebuilt workspaces
42- // * that the group has a quota of 0 by default, which users can adjust based on their needs.
40+ // Unique constraint violations are safely ignored (concurrent creation).
4341//
4442// ReconcileAll does not have an opinion on transaction or lock management. These responsibilities are left to the caller.
45- func (s StoreMembershipReconciler )ReconcileAll (ctx context.Context ,userID uuid.UUID ,presets []database.GetTemplatePresetsWithPrebuildsRow )error {
46- organizationMemberships ,err := s .store .GetOrganizationsByUserID (ctx , database.GetOrganizationsByUserIDParams {
47- UserID :userID ,
48- Deleted : sql.NullBool {
49- Bool :false ,
50- Valid :true ,
51- },
43+ func (s StoreMembershipReconciler )ReconcileAll (ctx context.Context ,userID uuid.UUID ,groupName string )error {
44+ orgStatuses ,err := s .store .GetOrganizationsWithPrebuildStatus (ctx , database.GetOrganizationsWithPrebuildStatusParams {
45+ UserID :userID ,
46+ GroupName :groupName ,
5247})
5348if err != nil {
54- return xerrors .Errorf ("determine prebuild organization membership: %w" ,err )
55- }
56-
57- orgMemberships := make (map [uuid.UUID ]struct {},0 )
58- defaultOrg ,err := s .store .GetDefaultOrganization (ctx )
59- if err != nil {
60- return xerrors .Errorf ("get default organization: %w" ,err )
61- }
62- orgMemberships [defaultOrg .ID ]= struct {}{}
63- for _ ,o := range organizationMemberships {
64- orgMemberships [o .ID ]= struct {}{}
49+ return xerrors .Errorf ("get organizations with prebuild status: %w" ,err )
6550}
6651
6752var membershipInsertionErrors error
68- for _ ,preset := range presets {
69- _ ,alreadyOrgMember := orgMemberships [preset .OrganizationID ]
70- if ! alreadyOrgMember {
71- // Add the organization to our list of memberships regardless of potential failure below
72- // to avoid a retry that will probably be doomed anyway.
73- orgMemberships [preset .OrganizationID ]= struct {}{}
74-
75- // Insert the missing membership
53+ for _ ,orgStatus := range orgStatuses {
54+ // Add user to org if needed
55+ if ! orgStatus .HasPrebuildUser {
7656_ ,err = s .store .InsertOrganizationMember (ctx , database.InsertOrganizationMemberParams {
77- OrganizationID :preset .OrganizationID ,
57+ OrganizationID :orgStatus .OrganizationID ,
7858UserID :userID ,
7959CreatedAt :s .clock .Now (),
8060UpdatedAt :s .clock .Now (),
8161Roles : []string {},
8262})
83- if err != nil {
84- membershipInsertionErrors = errors .Join (membershipInsertionErrors ,xerrors .Errorf ("insert membership for prebuilt workspaces: %w" ,err ))
85- continue
86- }
87- }
88-
89- // determine whether the org already has a prebuilds group
90- prebuildsGroupExists := true
91- prebuildsGroup ,err := s .store .GetGroupByOrgAndName (ctx , database.GetGroupByOrgAndNameParams {
92- OrganizationID :preset .OrganizationID ,
93- Name :PrebuiltWorkspacesGroupName ,
94- })
95- if err != nil {
96- if ! xerrors .Is (err ,sql .ErrNoRows ) {
97- membershipInsertionErrors = errors .Join (membershipInsertionErrors ,xerrors .Errorf ("get prebuilds group: %w" ,err ))
63+ // Unique violation means membership was created after status check, safe to ignore.
64+ if err != nil && ! database .IsUniqueViolation (err ) {
65+ membershipInsertionErrors = errors .Join (membershipInsertionErrors ,err )
9866continue
9967}
100- prebuildsGroupExists = false
10168}
10269
103- //if the prebuilds groupdoes not exist, create it
104- if ! prebuildsGroupExists {
105- // create a "prebuilds" group in the organization and add the system user to it
106- //this group will have a quota of 0 by default, which users can adjust based on their needs
107- prebuildsGroup ,err = s .store .InsertGroup (ctx , database.InsertGroupParams {
70+ //Create groupif it doesn't exist
71+ var groupID uuid. UUID
72+ if ! orgStatus . PrebuildsGroupID . Valid {
73+ //Group doesn't exist, create it
74+ group ,err : =s .store .InsertGroup (ctx , database.InsertGroupParams {
10875ID :uuid .New (),
10976Name :PrebuiltWorkspacesGroupName ,
11077DisplayName :PrebuiltWorkspacesGroupDisplayName ,
111- OrganizationID :preset .OrganizationID ,
78+ OrganizationID :orgStatus .OrganizationID ,
11279AvatarURL :"" ,
113- QuotaAllowance :0 ,// Default quota of 0, users should set this based on their needs
80+ QuotaAllowance :0 ,
11481})
115- if err != nil {
116- membershipInsertionErrors = errors .Join (membershipInsertionErrors ,xerrors .Errorf ("create prebuilds group: %w" ,err ))
82+ // Unique violation means membership was created after status check, safe to ignore.
83+ if err != nil && ! database .IsUniqueViolation (err ) {
84+ membershipInsertionErrors = errors .Join (membershipInsertionErrors ,err )
11785continue
11886}
87+ groupID = group .ID
88+ }else {
89+ // Group exists
90+ groupID = orgStatus .PrebuildsGroupID .UUID
11991}
12092
121- // add the system user to the prebuilds group
122- err = s .store .InsertGroupMember (ctx , database.InsertGroupMemberParams {
123- GroupID :prebuildsGroup .ID ,
124- UserID :userID ,
125- })
126- if err != nil {
127- // ignore unique violation errors as the user might already be in the group
128- if ! database .IsUniqueViolation (err ) {
129- membershipInsertionErrors = errors .Join (membershipInsertionErrors ,xerrors .Errorf ("add system user to prebuilds group: %w" ,err ))
93+ // Add user to group if needed
94+ if ! orgStatus .HasPrebuildUserInGroup {
95+ err = s .store .InsertGroupMember (ctx , database.InsertGroupMemberParams {
96+ GroupID :groupID ,
97+ UserID :userID ,
98+ })
99+ // Unique violation means membership was created after status check, safe to ignore.
100+ if err != nil && ! database .IsUniqueViolation (err ) {
101+ membershipInsertionErrors = errors .Join (membershipInsertionErrors ,err )
102+ continue
130103}
131104}
132105}
106+
133107return membershipInsertionErrors
134108}