Skip to content

Commit 3c215a8

Browse files
authored
feat: Allow admins to create workspaces (coder#4183)
Fixes coder#3263. This is now possible via the API, but still isn't possible via the UI.
1 parent 266a3b2 commit 3c215a8

File tree

9 files changed

+23
-21
lines changed

9 files changed

+23
-21
lines changed

cli/create.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ func create() *cobra.Command {
140140
}
141141

142142
after := time.Now()
143-
workspace, err := client.CreateWorkspace(cmd.Context(), organization.ID, codersdk.CreateWorkspaceRequest{
143+
workspace, err := client.CreateWorkspace(cmd.Context(), organization.ID, codersdk.Me, codersdk.CreateWorkspaceRequest{
144144
TemplateID: template.ID,
145145
Name: workspaceName,
146146
AutostartSchedule: schedSpec,

coderd/coderd.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,6 @@ func New(options *Options) *API {
292292
r.Get("/", api.templatesByOrganization)
293293
r.Get("/{templatename}", api.templateByOrganizationAndName)
294294
})
295-
r.Post("/workspaces", api.postWorkspacesByOrganization)
296295
r.Route("/members", func(r chi.Router) {
297296
r.Get("/roles", api.assignableOrgRoles)
298297
r.Route("/{user}", func(r chi.Router) {
@@ -301,6 +300,7 @@ func New(options *Options) *API {
301300
httpmw.ExtractOrganizationMemberParam(options.Database),
302301
)
303302
r.Put("/roles", api.putMemberRoles)
303+
r.Post("/workspaces", api.postWorkspacesByOrganization)
304304
})
305305
})
306306
})

coderd/coderdtest/authorize.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ func AGPLRoutes(a *AuthTester) (map[string]string, map[string]RouteCheck) {
233233
AssertAction: rbac.ActionRead,
234234
AssertObject: rbac.ResourceTemplate.InOrg(a.Template.OrganizationID),
235235
},
236-
"POST:/api/v2/organizations/{organization}/workspaces": {
236+
"POST:/api/v2/organizations/{organization}/members/{user}/workspaces": {
237237
AssertAction: rbac.ActionCreate,
238238
// No ID when creating
239239
AssertObject: workspaceRBACObj,

coderd/coderdtest/coderdtest.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ func CreateWorkspace(t *testing.T, client *codersdk.Client, organization uuid.UU
523523
for _, mutator := range mutators {
524524
mutator(&req)
525525
}
526-
workspace, err := client.CreateWorkspace(context.Background(), organization, req)
526+
workspace, err := client.CreateWorkspace(context.Background(), organization, codersdk.Me, req)
527527
require.NoError(t, err)
528528
return workspace
529529
}

coderd/workspaces.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
222222
organization = httpmw.OrganizationParam(r)
223223
apiKey = httpmw.APIKey(r)
224224
auditor = api.Auditor.Load()
225+
user = httpmw.UserParam(r)
225226
aReq, commitAudit = audit.InitRequest[database.Workspace](rw, &audit.RequestParams{
226227
Audit: *auditor,
227228
Log: api.Logger,
@@ -232,7 +233,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
232233
defer commitAudit()
233234

234235
if !api.Authorize(r, rbac.ActionCreate,
235-
rbac.ResourceWorkspace.InOrg(organization.ID).WithOwner(apiKey.UserID.String())) {
236+
rbac.ResourceWorkspace.InOrg(organization.ID).WithOwner(user.ID.String())) {
236237
httpapi.ResourceNotFound(rw)
237238
return
238239
}
@@ -292,7 +293,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
292293
}
293294

294295
workspace, err := api.Database.GetWorkspaceByOwnerIDAndName(ctx, database.GetWorkspaceByOwnerIDAndNameParams{
295-
OwnerID: apiKey.UserID,
296+
OwnerID: user.ID,
296297
Name: createWorkspace.Name,
297298
})
298299
if err == nil {
@@ -359,7 +360,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
359360
ID: uuid.New(),
360361
CreatedAt: now,
361362
UpdatedAt: now,
362-
OwnerID: apiKey.UserID,
363+
OwnerID: user.ID,
363364
OrganizationID: template.OrganizationID,
364365
TemplateID: template.ID,
365366
Name: createWorkspace.Name,
@@ -441,7 +442,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
441442
aReq.New = workspace
442443

443444
users, err := api.Database.GetUsersByIDs(ctx, database.GetUsersByIDsParams{
444-
IDs: []uuid.UUID{apiKey.UserID, workspaceBuild.InitiatorID},
445+
IDs: []uuid.UUID{user.ID, workspaceBuild.InitiatorID},
445446
})
446447
if err != nil {
447448
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
@@ -478,7 +479,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
478479
workspace,
479480
apiBuild,
480481
template,
481-
findUser(apiKey.UserID, users),
482+
findUser(user.ID, users),
482483
))
483484
}
484485

coderd/workspaces_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ func TestPostWorkspacesByOrganization(t *testing.T) {
156156
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
157157
defer cancel()
158158

159-
_, err := client.CreateWorkspace(ctx, user.OrganizationID, codersdk.CreateWorkspaceRequest{
159+
_, err := client.CreateWorkspace(ctx, user.OrganizationID, codersdk.Me, codersdk.CreateWorkspaceRequest{
160160
TemplateID: uuid.New(),
161161
Name: "workspace",
162162
})
@@ -183,7 +183,7 @@ func TestPostWorkspacesByOrganization(t *testing.T) {
183183
version := coderdtest.CreateTemplateVersion(t, other, org.ID, nil)
184184
template := coderdtest.CreateTemplate(t, other, org.ID, version.ID)
185185

186-
_, err = client.CreateWorkspace(ctx, first.OrganizationID, codersdk.CreateWorkspaceRequest{
186+
_, err = client.CreateWorkspace(ctx, first.OrganizationID, codersdk.Me, codersdk.CreateWorkspaceRequest{
187187
TemplateID: template.ID,
188188
Name: "workspace",
189189
})
@@ -205,7 +205,7 @@ func TestPostWorkspacesByOrganization(t *testing.T) {
205205
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
206206
defer cancel()
207207

208-
_, err := client.CreateWorkspace(ctx, user.OrganizationID, codersdk.CreateWorkspaceRequest{
208+
_, err := client.CreateWorkspace(ctx, user.OrganizationID, codersdk.Me, codersdk.CreateWorkspaceRequest{
209209
TemplateID: template.ID,
210210
Name: workspace.Name,
211211
})
@@ -285,7 +285,7 @@ func TestPostWorkspacesByOrganization(t *testing.T) {
285285
Name: "testing",
286286
TTLMillis: ptr.Ref((59 * time.Second).Milliseconds()),
287287
}
288-
_, err := client.CreateWorkspace(ctx, template.OrganizationID, req)
288+
_, err := client.CreateWorkspace(ctx, template.OrganizationID, codersdk.Me, req)
289289
require.Error(t, err)
290290
var apiErr *codersdk.Error
291291
require.ErrorAs(t, err, &apiErr)
@@ -311,7 +311,7 @@ func TestPostWorkspacesByOrganization(t *testing.T) {
311311
Name: "testing",
312312
TTLMillis: ptr.Ref(template.MaxTTLMillis + time.Minute.Milliseconds()),
313313
}
314-
_, err := client.CreateWorkspace(ctx, template.OrganizationID, req)
314+
_, err := client.CreateWorkspace(ctx, template.OrganizationID, codersdk.Me, req)
315315
require.Error(t, err)
316316
var apiErr *codersdk.Error
317317
require.ErrorAs(t, err, &apiErr)
@@ -338,7 +338,7 @@ func TestPostWorkspacesByOrganization(t *testing.T) {
338338
Name: "testing",
339339
AutostartSchedule: ptr.Ref("CRON_TZ=US/Central * * * * *"),
340340
}
341-
_, err := client.CreateWorkspace(ctx, template.OrganizationID, req)
341+
_, err := client.CreateWorkspace(ctx, template.OrganizationID, codersdk.Me, req)
342342
require.Error(t, err)
343343
var apiErr *codersdk.Error
344344
require.ErrorAs(t, err, &apiErr)
@@ -412,7 +412,7 @@ func TestWorkspaceByOwnerAndName(t *testing.T) {
412412

413413
// Given:
414414
// We recreate the workspace with the same name
415-
workspace, err = client.CreateWorkspace(ctx, user.OrganizationID, codersdk.CreateWorkspaceRequest{
415+
workspace, err = client.CreateWorkspace(ctx, user.OrganizationID, codersdk.Me, codersdk.CreateWorkspaceRequest{
416416
TemplateID: workspace.TemplateID,
417417
Name: workspace.Name,
418418
AutostartSchedule: workspace.AutostartSchedule,
@@ -772,7 +772,7 @@ func TestPostWorkspaceBuild(t *testing.T) {
772772
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
773773
defer cancel()
774774

775-
_, err := client.CreateWorkspace(ctx, user.OrganizationID, codersdk.CreateWorkspaceRequest{
775+
_, err := client.CreateWorkspace(ctx, user.OrganizationID, codersdk.Me, codersdk.CreateWorkspaceRequest{
776776
TemplateID: template.ID,
777777
Name: "workspace",
778778
})

codersdk/organizations.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,8 +198,8 @@ func (c *Client) TemplateByName(ctx context.Context, organizationID uuid.UUID, n
198198
}
199199

200200
// CreateWorkspace creates a new workspace for the template specified.
201-
func (c *Client) CreateWorkspace(ctx context.Context, organizationID uuid.UUID, request CreateWorkspaceRequest) (Workspace, error) {
202-
res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/organizations/%s/workspaces", organizationID), request)
201+
func (c *Client) CreateWorkspace(ctx context.Context, organizationID uuid.UUID, user string, request CreateWorkspaceRequest) (Workspace, error) {
202+
res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/organizations/%s/members/%s/workspaces", organizationID, user), request)
203203
if err != nil {
204204
return Workspace{}, err
205205
}

site/src/api/api.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,10 +302,11 @@ export const createUser = async (user: TypesGen.CreateUserRequest): Promise<Type
302302

303303
export const createWorkspace = async (
304304
organizationId: string,
305+
userId = "me",
305306
workspace: TypesGen.CreateWorkspaceRequest,
306307
): Promise<TypesGen.Workspace> => {
307308
const response = await axios.post<TypesGen.Workspace>(
308-
`/api/v2/organizations/${organizationId}/workspaces`,
309+
`/api/v2/organizations/${organizationId}/members/${userId}/workspaces`,
309310
workspace,
310311
)
311312
return response.data

site/src/xServices/createWorkspace/createWorkspaceXService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export const createWorkspaceMachine = createMachine(
128128
throw new Error("No create workspace request")
129129
}
130130

131-
return createWorkspace(organizationId, createWorkspaceRequest)
131+
return createWorkspace(organizationId, "me", createWorkspaceRequest)
132132
},
133133
},
134134
guards: {

0 commit comments

Comments
 (0)