Skip to content

Commit 17c486c

Browse files
authored
chore: ensure default org always exists (#12412)
* chore: ensure default org always exists First user just joins the org created by the migration
1 parent bc30c9c commit 17c486c

File tree

9 files changed

+71
-32
lines changed

9 files changed

+71
-32
lines changed

cli/templatelist_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ func TestTemplateList(t *testing.T) {
8787
t.Parallel()
8888
client := coderdtest.New(t, &coderdtest.Options{})
8989
owner := coderdtest.CreateFirstUser(t, client)
90+
91+
org, err := client.Organization(context.Background(), owner.OrganizationID)
92+
require.NoError(t, err)
93+
9094
templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
9195

9296
inv, root := clitest.New(t, "templates", "list")
@@ -107,7 +111,7 @@ func TestTemplateList(t *testing.T) {
107111
require.NoError(t, <-errC)
108112

109113
pty.ExpectMatch("No templates found in")
110-
pty.ExpectMatch(coderdtest.FirstUserParams.Username)
114+
pty.ExpectMatch(org.Name)
111115
pty.ExpectMatch("Create one:")
112116
})
113117
}

coderd/database/dbauthz/dbauthz.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ var (
232232
rbac.ResourceGroup.Type: {rbac.ActionCreate, rbac.ActionUpdate},
233233
rbac.ResourceRoleAssignment.Type: {rbac.ActionCreate, rbac.ActionDelete},
234234
rbac.ResourceSystem.Type: {rbac.WildcardSymbol},
235-
rbac.ResourceOrganization.Type: {rbac.ActionCreate},
235+
rbac.ResourceOrganization.Type: {rbac.ActionCreate, rbac.ActionRead},
236236
rbac.ResourceOrganizationMember.Type: {rbac.ActionCreate},
237237
rbac.ResourceOrgRoleAssignment.Type: {rbac.ActionCreate},
238238
rbac.ResourceProvisionerDaemon.Type: {rbac.ActionCreate, rbac.ActionUpdate},

coderd/database/dbauthz/dbauthz_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,7 @@ func (s *MethodTestSuite) TestOrganization() {
568568
check.Args(o.ID).Asserts(o, rbac.ActionRead).Returns(o)
569569
}))
570570
s.Run("GetDefaultOrganization", s.Subtest(func(db database.Store, check *expects) {
571-
o := dbgen.Organization(s.T(), db, database.Organization{})
571+
o, _ := db.GetDefaultOrganization(context.Background())
572572
check.Args().Asserts(o, rbac.ActionRead).Returns(o)
573573
}))
574574
s.Run("GetOrganizationByName", s.Subtest(func(db database.Store, check *expects) {
@@ -597,9 +597,10 @@ func (s *MethodTestSuite) TestOrganization() {
597597
check.Args(u.ID).Asserts(a, rbac.ActionRead, b, rbac.ActionRead).Returns(slice.New(a, b))
598598
}))
599599
s.Run("GetOrganizations", s.Subtest(func(db database.Store, check *expects) {
600+
def, _ := db.GetDefaultOrganization(context.Background())
600601
a := dbgen.Organization(s.T(), db, database.Organization{})
601602
b := dbgen.Organization(s.T(), db, database.Organization{})
602-
check.Args().Asserts(a, rbac.ActionRead, b, rbac.ActionRead).Returns(slice.New(a, b))
603+
check.Args().Asserts(def, rbac.ActionRead, a, rbac.ActionRead, b, rbac.ActionRead).Returns(slice.New(def, a, b))
603604
}))
604605
s.Run("GetOrganizationsByUserID", s.Subtest(func(db database.Store, check *expects) {
605606
u := dbgen.User(s.T(), db, database.User{})

coderd/database/dbmem/dbmem.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,17 @@ func New() database.Store {
7777
locks: map[int64]struct{}{},
7878
},
7979
}
80+
// Always start with a default org. Matching migration 198.
81+
_, err := q.InsertOrganization(context.Background(), database.InsertOrganizationParams{
82+
ID: uuid.New(),
83+
Name: "first-organization",
84+
Description: "Builtin default organization.",
85+
CreatedAt: dbtime.Now(),
86+
UpdatedAt: dbtime.Now(),
87+
})
88+
if err != nil {
89+
panic(fmt.Errorf("failed to create default organization: %w", err))
90+
}
8091
q.defaultProxyDisplayName = "Default"
8192
q.defaultProxyIconURL = "/emojis/1f3e1.png"
8293
return q
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-- There is no down. If the org is created, just let it be. Deleting an org feels dangerous in a migration.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-- This ensures a default organization always exists.
2+
INSERT INTO
3+
organizations(id, name, description, created_at, updated_at, is_default)
4+
SELECT
5+
-- Avoid calling it "default" as we are reserving that word as a keyword to fetch
6+
-- the default org regardless of the name.
7+
gen_random_uuid(),
8+
'first-organization',
9+
'Builtin default organization.',
10+
now(),
11+
now(),
12+
true
13+
WHERE
14+
-- Only insert if no organizations exist.
15+
NOT EXISTS (SELECT * FROM organizations);
16+

coderd/database/querier_test.go

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -506,20 +506,11 @@ func TestDefaultOrg(t *testing.T) {
506506
db := database.New(sqlDB)
507507
ctx := context.Background()
508508

509-
// Should start with 0 orgs
509+
// Should start with the default org
510510
all, err := db.GetOrganizations(ctx)
511511
require.NoError(t, err)
512-
require.Len(t, all, 0)
513-
514-
org, err := db.InsertOrganization(ctx, database.InsertOrganizationParams{
515-
ID: uuid.New(),
516-
Name: "default",
517-
Description: "",
518-
CreatedAt: dbtime.Now(),
519-
UpdatedAt: dbtime.Now(),
520-
})
521-
require.NoError(t, err)
522-
require.True(t, org.IsDefault, "first org should always be default")
512+
require.Len(t, all, 1)
513+
require.True(t, all[0].IsDefault, "first org should always be default")
523514
}
524515

525516
type tvArgs struct {

coderd/users.go

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -171,16 +171,37 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
171171
}
172172
}
173173

174+
//nolint:gocritic // needed to create first user
175+
defaultOrg, err := api.Database.GetDefaultOrganization(dbauthz.AsSystemRestricted(ctx))
176+
if err != nil {
177+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
178+
Message: "Internal error fetching default organization. If you are encountering this error, you will have to restart the Coder deployment.",
179+
Detail: err.Error(),
180+
})
181+
return
182+
}
183+
184+
//nolint:gocritic // ensure everyone group
185+
_, err = api.Database.InsertAllUsersGroup(dbauthz.AsSystemRestricted(ctx), defaultOrg.ID)
186+
// A unique constraint violation just means the group already exists.
187+
// This should not happen, but is ok if it does.
188+
if err != nil && !database.IsUniqueViolation(err) {
189+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
190+
Message: "Internal error creating all users group.",
191+
Detail: err.Error(),
192+
})
193+
return
194+
}
195+
174196
//nolint:gocritic // needed to create first user
175197
user, organizationID, err := api.CreateUser(dbauthz.AsSystemRestricted(ctx), api.Database, CreateUserRequest{
176198
CreateUserRequest: codersdk.CreateUserRequest{
177-
Email: createUser.Email,
178-
Username: createUser.Username,
179-
Password: createUser.Password,
180-
// Create an org for the first user.
181-
OrganizationID: uuid.Nil,
199+
Email: createUser.Email,
200+
Username: createUser.Username,
201+
Password: createUser.Password,
202+
OrganizationID: defaultOrg.ID,
182203
},
183-
CreateOrganization: true,
204+
CreateOrganization: false,
184205
LoginType: database.LoginTypePassword,
185206
})
186207
if err != nil {
@@ -1033,10 +1054,7 @@ func (api *API) userRoles(rw http.ResponseWriter, r *http.Request) {
10331054
}
10341055

10351056
for _, mem := range memberships {
1036-
// If we can read the org member, include the roles.
1037-
if err == nil {
1038-
resp.OrganizationRoles[mem.OrganizationID] = mem.Roles
1039-
}
1057+
resp.OrganizationRoles[mem.OrganizationID] = mem.Roles
10401058
}
10411059

10421060
httpapi.Write(ctx, rw, http.StatusOK, resp)
@@ -1247,9 +1265,8 @@ func (api *API) CreateUser(ctx context.Context, store database.Store, req Create
12471265
// TODO: When organizations are allowed to be created, we should
12481266
// come back to determining the default role of the person who
12491267
// creates the org. Until that happens, all users in an organization
1250-
// should be just regular members.
1251-
orgRoles = append(orgRoles, rbac.RoleOrgMember(req.OrganizationID))
1252-
1268+
// should be just regular members. Membership role is implied, and
1269+
// not required to be explicit.
12531270
_, err = tx.InsertAllUsersGroup(ctx, organization.ID)
12541271
if err != nil {
12551272
return xerrors.Errorf("create %q group: %w", database.EveryoneGroup, err)

coderd/users_test.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,9 +1093,7 @@ func TestInitialRoles(t *testing.T) {
10931093
rbac.RoleOwner(),
10941094
}, "should be a member and admin")
10951095

1096-
require.ElementsMatch(t, roles.OrganizationRoles[first.OrganizationID], []string{
1097-
rbac.RoleOrgMember(first.OrganizationID),
1098-
}, "should be a member and admin")
1096+
require.ElementsMatch(t, roles.OrganizationRoles[first.OrganizationID], []string{}, "should be a member")
10991097
}
11001098

11011099
func TestPutUserSuspend(t *testing.T) {

0 commit comments

Comments
 (0)