-
Notifications
You must be signed in to change notification settings - Fork 978
feat(enterprise/coderd): allow system users to be added to groups #19518
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6b3a731
4b63826
90b9078
af7c7cd
1cd68e3
4c0c4d4
93c46e9
b3728d3
efdfe9f
8204ec2
92760d1
471c29e
f9a5fbb
959c89e
1da8e34
cf6ee1c
b685245
e828366
adf7657
820cfd6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,11 @@ import ( | |
"github.com/coder/quartz" | ||
) | ||
|
||
const ( | ||
PrebuiltWorkspacesGroupName = "coderprebuiltworkspaces" | ||
PrebuiltWorkspacesGroupDisplayName = "Prebuilt Workspaces" | ||
) | ||
|
||
// StoreMembershipReconciler encapsulates the responsibility of ensuring that the prebuilds system user is a member of all | ||
// organizations for which prebuilt workspaces are requested. This is necessary because our data model requires that such | ||
// prebuilt workspaces belong to a member of the organization of their eventual claimant. | ||
|
@@ -27,11 +32,16 @@ func NewStoreMembershipReconciler(store database.Store, clock quartz.Clock) Stor | |
} | ||
} | ||
|
||
// ReconcileAll compares the current membership of a user to the membership required in order to create prebuilt workspaces. | ||
// If the user in question is not yet a member of an organization that needs prebuilt workspaces, ReconcileAll will create | ||
// the membership required. | ||
// ReconcileAll compares the current organization and group memberships of a user to the memberships required | ||
// in order to create prebuilt workspaces. If the user in question is not yet a member of an organization that | ||
// needs prebuilt workspaces, ReconcileAll will create the membership required. | ||
// | ||
// To facilitate quota management, ReconcileAll will ensure: | ||
// * the existence of a group (defined by PrebuiltWorkspacesGroupName) in each organization that needs prebuilt workspaces | ||
// * that the prebuilds system user belongs to the group in each organization that needs prebuilt workspaces | ||
// * that the group has a quota of 0 by default, which users can adjust based on their needs. | ||
// | ||
// This method does not have an opinion on transaction or lock management. These responsibilities are left to the caller. | ||
// ReconcileAll does not have an opinion on transaction or lock management. These responsibilities are left to the caller. | ||
func (s StoreMembershipReconciler) ReconcileAll(ctx context.Context, userID uuid.UUID, presets []database.GetTemplatePresetsWithPrebuildsRow) error { | ||
organizationMemberships, err := s.store.GetOrganizationsByUserID(ctx, database.GetOrganizationsByUserIDParams{ | ||
UserID: userID, | ||
|
@@ -44,37 +54,80 @@ func (s StoreMembershipReconciler) ReconcileAll(ctx context.Context, userID uuid | |
return xerrors.Errorf("determine prebuild organization membership: %w", err) | ||
} | ||
|
||
systemUserMemberships := make(map[uuid.UUID]struct{}, 0) | ||
orgMemberships := make(map[uuid.UUID]struct{}, 0) | ||
defaultOrg, err := s.store.GetDefaultOrganization(ctx) | ||
if err != nil { | ||
return xerrors.Errorf("get default organization: %w", err) | ||
} | ||
systemUserMemberships[defaultOrg.ID] = struct{}{} | ||
orgMemberships[defaultOrg.ID] = struct{}{} | ||
for _, o := range organizationMemberships { | ||
systemUserMemberships[o.ID] = struct{}{} | ||
orgMemberships[o.ID] = struct{}{} | ||
} | ||
|
||
var membershipInsertionErrors error | ||
for _, preset := range presets { | ||
_, alreadyMember := systemUserMemberships[preset.OrganizationID] | ||
if alreadyMember { | ||
continue | ||
_, alreadyOrgMember := orgMemberships[preset.OrganizationID] | ||
if !alreadyOrgMember { | ||
// Add the organization to our list of memberships regardless of potential failure below | ||
// to avoid a retry that will probably be doomed anyway. | ||
orgMemberships[preset.OrganizationID] = struct{}{} | ||
|
||
// Insert the missing membership | ||
_, err = s.store.InsertOrganizationMember(ctx, database.InsertOrganizationMemberParams{ | ||
OrganizationID: preset.OrganizationID, | ||
UserID: userID, | ||
CreatedAt: s.clock.Now(), | ||
UpdatedAt: s.clock.Now(), | ||
Roles: []string{}, | ||
}) | ||
if err != nil { | ||
membershipInsertionErrors = errors.Join(membershipInsertionErrors, xerrors.Errorf("insert membership for prebuilt workspaces: %w", err)) | ||
continue | ||
} | ||
} | ||
// Add the organization to our list of memberships regardless of potential failure below | ||
// to avoid a retry that will probably be doomed anyway. | ||
systemUserMemberships[preset.OrganizationID] = struct{}{} | ||
|
||
// Insert the missing membership | ||
_, err = s.store.InsertOrganizationMember(ctx, database.InsertOrganizationMemberParams{ | ||
// determine whether the org already has a prebuilds group | ||
prebuildsGroupExists := true | ||
prebuildsGroup, err := s.store.GetGroupByOrgAndName(ctx, database.GetGroupByOrgAndNameParams{ | ||
OrganizationID: preset.OrganizationID, | ||
UserID: userID, | ||
CreatedAt: s.clock.Now(), | ||
UpdatedAt: s.clock.Now(), | ||
Roles: []string{}, | ||
Name: PrebuiltWorkspacesGroupName, | ||
}) | ||
if err != nil { | ||
if !xerrors.Is(err, sql.ErrNoRows) { | ||
membershipInsertionErrors = errors.Join(membershipInsertionErrors, xerrors.Errorf("get prebuilds group: %w", err)) | ||
continue | ||
} | ||
prebuildsGroupExists = false | ||
} | ||
|
||
// if the prebuilds group does not exist, create it | ||
if !prebuildsGroupExists { | ||
// create a "prebuilds" group in the organization and add the system user to it | ||
// this group will have a quota of 0 by default, which users can adjust based on their needs | ||
prebuildsGroup, err = s.store.InsertGroup(ctx, database.InsertGroupParams{ | ||
ID: uuid.New(), | ||
Name: PrebuiltWorkspacesGroupName, | ||
DisplayName: PrebuiltWorkspacesGroupDisplayName, | ||
OrganizationID: preset.OrganizationID, | ||
AvatarURL: "", | ||
QuotaAllowance: 0, // Default quota of 0, users should set this based on their needs | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct me if I'm wrong, but does this mean that once this change is rolled out, no more prebuilt workspaces will be able to be provisioned until the Quota Allowance is changed? Or is the prebuilds user still part of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the prebuilds user is still a member of the everyone group, so this shouldn't be too much of a breaking change. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, thanks for the clarification! nit(non-blocking): It might be helpful to also add this info to the documentation, that the prebuilds user is a member of the |
||
}) | ||
if err != nil { | ||
membershipInsertionErrors = errors.Join(membershipInsertionErrors, xerrors.Errorf("create prebuilds group: %w", err)) | ||
continue | ||
} | ||
} | ||
|
||
// add the system user to the prebuilds group | ||
err = s.store.InsertGroupMember(ctx, database.InsertGroupMemberParams{ | ||
GroupID: prebuildsGroup.ID, | ||
UserID: userID, | ||
}) | ||
if err != nil { | ||
membershipInsertionErrors = errors.Join(membershipInsertionErrors, xerrors.Errorf("insert membership for prebuilt workspaces: %w", err)) | ||
continue | ||
// ignore unique violation errors as the user might already be in the group | ||
if !database.IsUniqueViolation(err) { | ||
membershipInsertionErrors = errors.Join(membershipInsertionErrors, xerrors.Errorf("add system user to prebuilds group: %w", err)) | ||
} | ||
} | ||
} | ||
return membershipInsertionErrors | ||
|
Uh oh!
There was an error while loading. Please reload this page.