Skip to content

Commit 912bea7

Browse files
committed
aaaaahhhaaaa
1 parent 58b810f commit 912bea7

File tree

10 files changed

+204
-53
lines changed

10 files changed

+204
-53
lines changed

coderd/apidoc/docs.go

Lines changed: 48 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/audit/request.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ type Request[T Auditable] struct {
5151
Action database.AuditAction
5252
}
5353

54+
func (r *Request[T]) UpdateOrganizationID(id uuid.UUID) {
55+
r.params.OrganizationID = id
56+
}
57+
5458
type BackgroundAuditParams[T Auditable] struct {
5559
Audit Auditor
5660
Log slog.Logger

coderd/coderd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,7 @@ func New(options *Options) *API {
10431043
r.Get("/", api.organizationsByUser)
10441044
r.Get("/{organizationname}", api.organizationByUserAndName)
10451045
})
1046+
r.Post("/workspaces", api.postUserWorkspaces)
10461047
r.Route("/workspace/{workspacename}", func(r chi.Router) {
10471048
r.Get("/", api.workspaceByOwnerAndName)
10481049
r.Get("/builds/{buildnumber}", api.workspaceBuildByBuildNumber)

coderd/workspaces.go

Lines changed: 90 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request)
340340
// @Description specify either the Template ID or the Template Version ID,
341341
// @Description not both. If the Template ID is specified, the active version
342342
// @Description of the template will be used.
343+
// @Deprecated
343344
// @ID create-user-workspace-by-organization
344345
// @Security CoderSessionToken
345346
// @Accept json
@@ -353,9 +354,9 @@ func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request)
353354
func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Request) {
354355
var (
355356
ctx = r.Context()
356-
organization = httpmw.OrganizationParam(r)
357357
apiKey = httpmw.APIKey(r)
358358
auditor = api.Auditor.Load()
359+
organization = httpmw.OrganizationParam(r)
359360
member = httpmw.OrganizationMemberParam(r)
360361
workspaceResourceInfo = audit.AdditionalFields{
361362
WorkspaceOwner: member.Username,
@@ -380,15 +381,78 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
380381
return
381382
}
382383

383-
var createWorkspace codersdk.CreateWorkspaceRequest
384-
if !httpapi.Read(ctx, rw, r, &createWorkspace) {
384+
var req codersdk.CreateWorkspaceRequest
385+
if !httpapi.Read(ctx, rw, r, &req) {
385386
return
386387
}
387388

389+
user := database.User{
390+
ID: member.UserID,
391+
Username: member.Username,
392+
AvatarURL: member.AvatarURL,
393+
}
394+
395+
createWorkspace(ctx, aReq, apiKey.UserID, api, user, req, rw, r)
396+
}
397+
398+
// Create a new workspace for the currently authenticated user.
399+
//
400+
// @Summary Create user workspace by organization
401+
// @Description Create a new workspace using a template. The request must
402+
// @Description specify either the Template ID or the Template Version ID,
403+
// @Description not both. If the Template ID is specified, the active version
404+
// @Description of the template will be used.
405+
// @ID create-user-workspace
406+
// @Security CoderSessionToken
407+
// @Accept json
408+
// @Produce json
409+
// @Tags Workspaces
410+
// @Param user path string true "Username, UUID, or me"
411+
// @Param request body codersdk.CreateWorkspaceRequest true "Create workspace request"
412+
// @Success 200 {object} codersdk.Workspace
413+
// @Router /users/{user}/workspaces [post]
414+
func (api *API) postUserWorkspaces(rw http.ResponseWriter, r *http.Request) {
415+
var (
416+
ctx = r.Context()
417+
apiKey = httpmw.APIKey(r)
418+
auditor = api.Auditor.Load()
419+
user = httpmw.UserParam(r)
420+
)
421+
422+
aReq, commitAudit := audit.InitRequest[database.Workspace](rw, &audit.RequestParams{
423+
Audit: *auditor,
424+
Log: api.Logger,
425+
Request: r,
426+
Action: database.AuditActionCreate,
427+
AdditionalFields: audit.AdditionalFields{
428+
WorkspaceOwner: user.Username,
429+
},
430+
})
431+
432+
defer commitAudit()
433+
434+
var req codersdk.CreateWorkspaceRequest
435+
if !httpapi.Read(ctx, rw, r, &req) {
436+
return
437+
}
438+
439+
createWorkspace(ctx, aReq, apiKey.UserID, api, user, req, rw, r)
440+
}
441+
442+
func createWorkspace(
443+
ctx context.Context,
444+
auditReq *audit.Request[database.Workspace],
445+
initiatorID uuid.UUID,
446+
api *API,
447+
user database.User,
448+
req codersdk.CreateWorkspaceRequest,
449+
rw http.ResponseWriter,
450+
r *http.Request,
451+
) {
388452
// If we were given a `TemplateVersionID`, we need to determine the `TemplateID` from it.
389-
templateID := createWorkspace.TemplateID
453+
templateID := req.TemplateID
390454
if templateID == uuid.Nil {
391-
templateVersion, err := api.Database.GetTemplateVersionByID(ctx, createWorkspace.TemplateVersionID)
455+
templateVersion, err := api.Database.GetTemplateVersionByID(ctx, req.TemplateVersionID)
392456
if errors.Is(err, sql.ErrNoRows) {
393457
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
394458
Message: fmt.Sprintf("Template version %q doesn't exist.", templateID.String()),
@@ -446,6 +510,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
446510
})
447511
return
448512
}
513+
auditReq.UpdateOrganizationID(template.OrganizationID)
449514

450515
templateAccessControl := (*(api.AccessControlStore.Load())).GetTemplateAccessControl(template)
451516
if templateAccessControl.IsDeprecated() {
@@ -458,14 +523,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
458523
return
459524
}
460525

461-
if organization.ID != template.OrganizationID {
462-
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
463-
Message: fmt.Sprintf("Template is not in organization %q.", organization.Name),
464-
})
465-
return
466-
}
467-
468-
dbAutostartSchedule, err := validWorkspaceSchedule(createWorkspace.AutostartSchedule)
526+
dbAutostartSchedule, err := validWorkspaceSchedule(req.AutostartSchedule)
469527
if err != nil {
470528
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
471529
Message: "Invalid Autostart Schedule.",
@@ -483,7 +541,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
483541
return
484542
}
485543

486-
dbTTL, err := validWorkspaceTTLMillis(createWorkspace.TTLMillis, templateSchedule.DefaultTTL)
544+
dbTTL, err := validWorkspaceTTLMillis(req.TTLMillis, templateSchedule.DefaultTTL)
487545
if err != nil {
488546
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
489547
Message: "Invalid Workspace Time to Shutdown.",
@@ -494,8 +552,8 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
494552

495553
// back-compatibility: default to "never" if not included.
496554
dbAU := database.AutomaticUpdatesNever
497-
if createWorkspace.AutomaticUpdates != "" {
498-
dbAU, err = validWorkspaceAutomaticUpdates(createWorkspace.AutomaticUpdates)
555+
if req.AutomaticUpdates != "" {
556+
dbAU, err = validWorkspaceAutomaticUpdates(req.AutomaticUpdates)
499557
if err != nil {
500558
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
501559
Message: "Invalid Workspace Automatic Updates setting.",
@@ -509,13 +567,13 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
509567
// read other workspaces. Ideally we check the error on create and look for
510568
// a postgres conflict error.
511569
workspace, err := api.Database.GetWorkspaceByOwnerIDAndName(ctx, database.GetWorkspaceByOwnerIDAndNameParams{
512-
OwnerID: member.UserID,
513-
Name: createWorkspace.Name,
570+
OwnerID: user.ID,
571+
Name: req.Name,
514572
})
515573
if err == nil {
516574
// If the workspace already exists, don't allow creation.
517575
httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{
518-
Message: fmt.Sprintf("Workspace %q already exists.", createWorkspace.Name),
576+
Message: fmt.Sprintf("Workspace %q already exists.", req.Name),
519577
Validations: []codersdk.ValidationError{{
520578
Field: "name",
521579
Detail: "This value is already in use and should be unique.",
@@ -525,7 +583,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
525583
}
526584
if err != nil && !errors.Is(err, sql.ErrNoRows) {
527585
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
528-
Message: fmt.Sprintf("Internal error fetching workspace by name %q.", createWorkspace.Name),
586+
Message: fmt.Sprintf("Internal error fetching workspace by name %q.", req.Name),
529587
Detail: err.Error(),
530588
})
531589
return
@@ -542,10 +600,10 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
542600
ID: uuid.New(),
543601
CreatedAt: now,
544602
UpdatedAt: now,
545-
OwnerID: member.UserID,
603+
OwnerID: user.ID,
546604
OrganizationID: template.OrganizationID,
547605
TemplateID: template.ID,
548-
Name: createWorkspace.Name,
606+
Name: req.Name,
549607
AutostartSchedule: dbAutostartSchedule,
550608
Ttl: dbTTL,
551609
// The workspaces page will sort by last used at, and it's useful to
@@ -559,11 +617,11 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
559617

560618
builder := wsbuilder.New(workspace, database.WorkspaceTransitionStart).
561619
Reason(database.BuildReasonInitiator).
562-
Initiator(apiKey.UserID).
620+
Initiator(initiatorID).
563621
ActiveVersion().
564-
RichParameterValues(createWorkspace.RichParameterValues)
565-
if createWorkspace.TemplateVersionID != uuid.Nil {
566-
builder = builder.VersionID(createWorkspace.TemplateVersionID)
622+
RichParameterValues(req.RichParameterValues)
623+
if req.TemplateVersionID != uuid.Nil {
624+
builder = builder.VersionID(req.TemplateVersionID)
567625
}
568626

569627
workspaceBuild, provisionerJob, err = builder.Build(
@@ -596,7 +654,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
596654
// Client probably doesn't care about this error, so just log it.
597655
api.Logger.Error(ctx, "failed to post provisioner job to pubsub", slog.Error(err))
598656
}
599-
aReq.New = workspace
657+
auditReq.New = workspace
600658

601659
api.Telemetry.Report(&telemetry.Snapshot{
602660
Workspaces: []telemetry.Workspace{telemetry.ConvertWorkspace(workspace)},
@@ -610,8 +668,8 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
610668
ProvisionerJob: *provisionerJob,
611669
QueuePosition: 0,
612670
},
613-
member.Username,
614-
member.AvatarURL,
671+
user.Username,
672+
user.AvatarURL,
615673
[]database.WorkspaceResource{},
616674
[]database.WorkspaceResourceMetadatum{},
617675
[]database.WorkspaceAgent{},
@@ -629,12 +687,12 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
629687
}
630688

631689
w, err := convertWorkspace(
632-
apiKey.UserID,
690+
initiatorID,
633691
workspace,
634692
apiBuild,
635693
template,
636-
member.Username,
637-
member.AvatarURL,
694+
user.Username,
695+
user.AvatarURL,
638696
api.Options.AllowWorkspaceRenames,
639697
)
640698
if err != nil {

0 commit comments

Comments
 (0)