mirror of https://github.com/coder/coder.git
chore: ensure default org always exists (#12412)
* chore: ensure default org always exists First user just joins the org created by the migration
This commit is contained in:
parent
bc30c9c013
commit
17c486c5e6
|
@ -87,6 +87,10 @@ func TestTemplateList(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
client := coderdtest.New(t, &coderdtest.Options{})
|
client := coderdtest.New(t, &coderdtest.Options{})
|
||||||
owner := coderdtest.CreateFirstUser(t, client)
|
owner := coderdtest.CreateFirstUser(t, client)
|
||||||
|
|
||||||
|
org, err := client.Organization(context.Background(), owner.OrganizationID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
|
templateAdmin, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, rbac.RoleTemplateAdmin())
|
||||||
|
|
||||||
inv, root := clitest.New(t, "templates", "list")
|
inv, root := clitest.New(t, "templates", "list")
|
||||||
|
@ -107,7 +111,7 @@ func TestTemplateList(t *testing.T) {
|
||||||
require.NoError(t, <-errC)
|
require.NoError(t, <-errC)
|
||||||
|
|
||||||
pty.ExpectMatch("No templates found in")
|
pty.ExpectMatch("No templates found in")
|
||||||
pty.ExpectMatch(coderdtest.FirstUserParams.Username)
|
pty.ExpectMatch(org.Name)
|
||||||
pty.ExpectMatch("Create one:")
|
pty.ExpectMatch("Create one:")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -232,7 +232,7 @@ var (
|
||||||
rbac.ResourceGroup.Type: {rbac.ActionCreate, rbac.ActionUpdate},
|
rbac.ResourceGroup.Type: {rbac.ActionCreate, rbac.ActionUpdate},
|
||||||
rbac.ResourceRoleAssignment.Type: {rbac.ActionCreate, rbac.ActionDelete},
|
rbac.ResourceRoleAssignment.Type: {rbac.ActionCreate, rbac.ActionDelete},
|
||||||
rbac.ResourceSystem.Type: {rbac.WildcardSymbol},
|
rbac.ResourceSystem.Type: {rbac.WildcardSymbol},
|
||||||
rbac.ResourceOrganization.Type: {rbac.ActionCreate},
|
rbac.ResourceOrganization.Type: {rbac.ActionCreate, rbac.ActionRead},
|
||||||
rbac.ResourceOrganizationMember.Type: {rbac.ActionCreate},
|
rbac.ResourceOrganizationMember.Type: {rbac.ActionCreate},
|
||||||
rbac.ResourceOrgRoleAssignment.Type: {rbac.ActionCreate},
|
rbac.ResourceOrgRoleAssignment.Type: {rbac.ActionCreate},
|
||||||
rbac.ResourceProvisionerDaemon.Type: {rbac.ActionCreate, rbac.ActionUpdate},
|
rbac.ResourceProvisionerDaemon.Type: {rbac.ActionCreate, rbac.ActionUpdate},
|
||||||
|
|
|
@ -568,7 +568,7 @@ func (s *MethodTestSuite) TestOrganization() {
|
||||||
check.Args(o.ID).Asserts(o, rbac.ActionRead).Returns(o)
|
check.Args(o.ID).Asserts(o, rbac.ActionRead).Returns(o)
|
||||||
}))
|
}))
|
||||||
s.Run("GetDefaultOrganization", s.Subtest(func(db database.Store, check *expects) {
|
s.Run("GetDefaultOrganization", s.Subtest(func(db database.Store, check *expects) {
|
||||||
o := dbgen.Organization(s.T(), db, database.Organization{})
|
o, _ := db.GetDefaultOrganization(context.Background())
|
||||||
check.Args().Asserts(o, rbac.ActionRead).Returns(o)
|
check.Args().Asserts(o, rbac.ActionRead).Returns(o)
|
||||||
}))
|
}))
|
||||||
s.Run("GetOrganizationByName", s.Subtest(func(db database.Store, check *expects) {
|
s.Run("GetOrganizationByName", s.Subtest(func(db database.Store, check *expects) {
|
||||||
|
@ -597,9 +597,10 @@ func (s *MethodTestSuite) TestOrganization() {
|
||||||
check.Args(u.ID).Asserts(a, rbac.ActionRead, b, rbac.ActionRead).Returns(slice.New(a, b))
|
check.Args(u.ID).Asserts(a, rbac.ActionRead, b, rbac.ActionRead).Returns(slice.New(a, b))
|
||||||
}))
|
}))
|
||||||
s.Run("GetOrganizations", s.Subtest(func(db database.Store, check *expects) {
|
s.Run("GetOrganizations", s.Subtest(func(db database.Store, check *expects) {
|
||||||
|
def, _ := db.GetDefaultOrganization(context.Background())
|
||||||
a := dbgen.Organization(s.T(), db, database.Organization{})
|
a := dbgen.Organization(s.T(), db, database.Organization{})
|
||||||
b := dbgen.Organization(s.T(), db, database.Organization{})
|
b := dbgen.Organization(s.T(), db, database.Organization{})
|
||||||
check.Args().Asserts(a, rbac.ActionRead, b, rbac.ActionRead).Returns(slice.New(a, b))
|
check.Args().Asserts(def, rbac.ActionRead, a, rbac.ActionRead, b, rbac.ActionRead).Returns(slice.New(def, a, b))
|
||||||
}))
|
}))
|
||||||
s.Run("GetOrganizationsByUserID", s.Subtest(func(db database.Store, check *expects) {
|
s.Run("GetOrganizationsByUserID", s.Subtest(func(db database.Store, check *expects) {
|
||||||
u := dbgen.User(s.T(), db, database.User{})
|
u := dbgen.User(s.T(), db, database.User{})
|
||||||
|
|
|
@ -77,6 +77,17 @@ func New() database.Store {
|
||||||
locks: map[int64]struct{}{},
|
locks: map[int64]struct{}{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
// Always start with a default org. Matching migration 198.
|
||||||
|
_, err := q.InsertOrganization(context.Background(), database.InsertOrganizationParams{
|
||||||
|
ID: uuid.New(),
|
||||||
|
Name: "first-organization",
|
||||||
|
Description: "Builtin default organization.",
|
||||||
|
CreatedAt: dbtime.Now(),
|
||||||
|
UpdatedAt: dbtime.Now(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("failed to create default organization: %w", err))
|
||||||
|
}
|
||||||
q.defaultProxyDisplayName = "Default"
|
q.defaultProxyDisplayName = "Default"
|
||||||
q.defaultProxyIconURL = "/emojis/1f3e1.png"
|
q.defaultProxyIconURL = "/emojis/1f3e1.png"
|
||||||
return q
|
return q
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
-- There is no down. If the org is created, just let it be. Deleting an org feels dangerous in a migration.
|
|
@ -0,0 +1,16 @@
|
||||||
|
-- This ensures a default organization always exists.
|
||||||
|
INSERT INTO
|
||||||
|
organizations(id, name, description, created_at, updated_at, is_default)
|
||||||
|
SELECT
|
||||||
|
-- Avoid calling it "default" as we are reserving that word as a keyword to fetch
|
||||||
|
-- the default org regardless of the name.
|
||||||
|
gen_random_uuid(),
|
||||||
|
'first-organization',
|
||||||
|
'Builtin default organization.',
|
||||||
|
now(),
|
||||||
|
now(),
|
||||||
|
true
|
||||||
|
WHERE
|
||||||
|
-- Only insert if no organizations exist.
|
||||||
|
NOT EXISTS (SELECT * FROM organizations);
|
||||||
|
|
|
@ -506,20 +506,11 @@ func TestDefaultOrg(t *testing.T) {
|
||||||
db := database.New(sqlDB)
|
db := database.New(sqlDB)
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// Should start with 0 orgs
|
// Should start with the default org
|
||||||
all, err := db.GetOrganizations(ctx)
|
all, err := db.GetOrganizations(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, all, 0)
|
require.Len(t, all, 1)
|
||||||
|
require.True(t, all[0].IsDefault, "first org should always be default")
|
||||||
org, err := db.InsertOrganization(ctx, database.InsertOrganizationParams{
|
|
||||||
ID: uuid.New(),
|
|
||||||
Name: "default",
|
|
||||||
Description: "",
|
|
||||||
CreatedAt: dbtime.Now(),
|
|
||||||
UpdatedAt: dbtime.Now(),
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, org.IsDefault, "first org should always be default")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type tvArgs struct {
|
type tvArgs struct {
|
||||||
|
|
|
@ -171,16 +171,37 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:gocritic // needed to create first user
|
||||||
|
defaultOrg, err := api.Database.GetDefaultOrganization(dbauthz.AsSystemRestricted(ctx))
|
||||||
|
if err != nil {
|
||||||
|
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||||
|
Message: "Internal error fetching default organization. If you are encountering this error, you will have to restart the Coder deployment.",
|
||||||
|
Detail: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:gocritic // ensure everyone group
|
||||||
|
_, err = api.Database.InsertAllUsersGroup(dbauthz.AsSystemRestricted(ctx), defaultOrg.ID)
|
||||||
|
// A unique constraint violation just means the group already exists.
|
||||||
|
// This should not happen, but is ok if it does.
|
||||||
|
if err != nil && !database.IsUniqueViolation(err) {
|
||||||
|
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||||
|
Message: "Internal error creating all users group.",
|
||||||
|
Detail: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
//nolint:gocritic // needed to create first user
|
//nolint:gocritic // needed to create first user
|
||||||
user, organizationID, err := api.CreateUser(dbauthz.AsSystemRestricted(ctx), api.Database, CreateUserRequest{
|
user, organizationID, err := api.CreateUser(dbauthz.AsSystemRestricted(ctx), api.Database, CreateUserRequest{
|
||||||
CreateUserRequest: codersdk.CreateUserRequest{
|
CreateUserRequest: codersdk.CreateUserRequest{
|
||||||
Email: createUser.Email,
|
Email: createUser.Email,
|
||||||
Username: createUser.Username,
|
Username: createUser.Username,
|
||||||
Password: createUser.Password,
|
Password: createUser.Password,
|
||||||
// Create an org for the first user.
|
OrganizationID: defaultOrg.ID,
|
||||||
OrganizationID: uuid.Nil,
|
|
||||||
},
|
},
|
||||||
CreateOrganization: true,
|
CreateOrganization: false,
|
||||||
LoginType: database.LoginTypePassword,
|
LoginType: database.LoginTypePassword,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1033,10 +1054,7 @@ func (api *API) userRoles(rw http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, mem := range memberships {
|
for _, mem := range memberships {
|
||||||
// If we can read the org member, include the roles.
|
resp.OrganizationRoles[mem.OrganizationID] = mem.Roles
|
||||||
if err == nil {
|
|
||||||
resp.OrganizationRoles[mem.OrganizationID] = mem.Roles
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
httpapi.Write(ctx, rw, http.StatusOK, resp)
|
httpapi.Write(ctx, rw, http.StatusOK, resp)
|
||||||
|
@ -1247,9 +1265,8 @@ func (api *API) CreateUser(ctx context.Context, store database.Store, req Create
|
||||||
// TODO: When organizations are allowed to be created, we should
|
// TODO: When organizations are allowed to be created, we should
|
||||||
// come back to determining the default role of the person who
|
// come back to determining the default role of the person who
|
||||||
// creates the org. Until that happens, all users in an organization
|
// creates the org. Until that happens, all users in an organization
|
||||||
// should be just regular members.
|
// should be just regular members. Membership role is implied, and
|
||||||
orgRoles = append(orgRoles, rbac.RoleOrgMember(req.OrganizationID))
|
// not required to be explicit.
|
||||||
|
|
||||||
_, err = tx.InsertAllUsersGroup(ctx, organization.ID)
|
_, err = tx.InsertAllUsersGroup(ctx, organization.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("create %q group: %w", database.EveryoneGroup, err)
|
return xerrors.Errorf("create %q group: %w", database.EveryoneGroup, err)
|
||||||
|
|
|
@ -1093,9 +1093,7 @@ func TestInitialRoles(t *testing.T) {
|
||||||
rbac.RoleOwner(),
|
rbac.RoleOwner(),
|
||||||
}, "should be a member and admin")
|
}, "should be a member and admin")
|
||||||
|
|
||||||
require.ElementsMatch(t, roles.OrganizationRoles[first.OrganizationID], []string{
|
require.ElementsMatch(t, roles.OrganizationRoles[first.OrganizationID], []string{}, "should be a member")
|
||||||
rbac.RoleOrgMember(first.OrganizationID),
|
|
||||||
}, "should be a member and admin")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPutUserSuspend(t *testing.T) {
|
func TestPutUserSuspend(t *testing.T) {
|
||||||
|
|
Loading…
Reference in New Issue