@@ -17,6 +17,7 @@ import (
17
17
"github.com/coder/coder/v2/coderd/audit"
18
18
"github.com/coder/coder/v2/coderd/database"
19
19
"github.com/coder/coder/v2/coderd/httpapi"
20
+ "github.com/coder/coder/v2/coderd/httpapi/httperror"
20
21
"github.com/coder/coder/v2/coderd/httpmw"
21
22
"github.com/coder/coder/v2/coderd/rbac"
22
23
"github.com/coder/coder/v2/coderd/rbac/policy"
@@ -154,8 +155,9 @@ func (api *API) tasksCreate(rw http.ResponseWriter, r *http.Request) {
154
155
// This can be optimized. It exists as it is now for code simplicity.
155
156
// The most common case is to create a workspace for 'Me'. Which does
156
157
// not enter this code branch.
157
- template , ok := requestTemplate (ctx , rw , createReq , api .Database )
158
- if ! ok {
158
+ template , err := requestTemplate (ctx , createReq , api .Database )
159
+ if err != nil {
160
+ httperror .WriteResponseError (ctx , rw , err )
159
161
return
160
162
}
161
163
@@ -186,9 +188,72 @@ func (api *API) tasksCreate(rw http.ResponseWriter, r *http.Request) {
186
188
WorkspaceOwner : owner .Username ,
187
189
},
188
190
})
189
-
190
191
defer commitAudit ()
191
- createWorkspace (ctx , aReq , apiKey .UserID , api , owner , createReq , rw , r )
192
+ w , err := createWorkspace (ctx , aReq , apiKey .UserID , api , owner , createReq , r )
193
+ if err != nil {
194
+ httperror .WriteResponseError (ctx , rw , err )
195
+ return
196
+ }
197
+
198
+ task := taskFromWorkspace (w , req .Prompt )
199
+ httpapi .Write (ctx , rw , http .StatusCreated , task )
200
+ }
201
+
202
+ func taskFromWorkspace (ws codersdk.Workspace , initialPrompt string ) codersdk.Task {
203
+ // TODO(DanielleMaywood):
204
+ // This just picks up the first agent it discovers.
205
+ // This approach _might_ break when a task has multiple agents,
206
+ // depending on which agent was found first.
207
+ //
208
+ // We explicitly do not have support for running tasks
209
+ // inside of a sub agent at the moment, so we can be sure
210
+ // that any sub agents are not the agent we're looking for.
211
+ var taskAgentID uuid.NullUUID
212
+ var taskAgentLifecycle * codersdk.WorkspaceAgentLifecycle
213
+ var taskAgentHealth * codersdk.WorkspaceAgentHealth
214
+ for _ , resource := range ws .LatestBuild .Resources {
215
+ for _ , agent := range resource .Agents {
216
+ if agent .ParentID .Valid {
217
+ continue
218
+ }
219
+
220
+ taskAgentID = uuid.NullUUID {Valid : true , UUID : agent .ID }
221
+ taskAgentLifecycle = & agent .LifecycleState
222
+ taskAgentHealth = & agent .Health
223
+ break
224
+ }
225
+ }
226
+
227
+ var currentState * codersdk.TaskStateEntry
228
+ if ws .LatestAppStatus != nil {
229
+ currentState = & codersdk.TaskStateEntry {
230
+ Timestamp : ws .LatestAppStatus .CreatedAt ,
231
+ State : codersdk .TaskState (ws .LatestAppStatus .State ),
232
+ Message : ws .LatestAppStatus .Message ,
233
+ URI : ws .LatestAppStatus .URI ,
234
+ }
235
+ }
236
+
237
+ return codersdk.Task {
238
+ ID : ws .ID ,
239
+ OrganizationID : ws .OrganizationID ,
240
+ OwnerID : ws .OwnerID ,
241
+ OwnerName : ws .OwnerName ,
242
+ Name : ws .Name ,
243
+ TemplateID : ws .TemplateID ,
244
+ TemplateName : ws .TemplateName ,
245
+ TemplateDisplayName : ws .TemplateDisplayName ,
246
+ TemplateIcon : ws .TemplateIcon ,
247
+ WorkspaceID : uuid.NullUUID {Valid : true , UUID : ws .ID },
248
+ WorkspaceAgentID : taskAgentID ,
249
+ WorkspaceAgentLifecycle : taskAgentLifecycle ,
250
+ WorkspaceAgentHealth : taskAgentHealth ,
251
+ CreatedAt : ws .CreatedAt ,
252
+ UpdatedAt : ws .UpdatedAt ,
253
+ InitialPrompt : initialPrompt ,
254
+ Status : ws .LatestBuild .Status ,
255
+ CurrentState : currentState ,
256
+ }
192
257
}
193
258
194
259
// tasksFromWorkspaces converts a slice of API workspaces into tasks, fetching
@@ -213,60 +278,7 @@ func (api *API) tasksFromWorkspaces(ctx context.Context, apiWorkspaces []codersd
213
278
214
279
tasks := make ([]codersdk.Task , 0 , len (apiWorkspaces ))
215
280
for _ , ws := range apiWorkspaces {
216
- // TODO(DanielleMaywood):
217
- // This just picks up the first agent it discovers.
218
- // This approach _might_ break when a task has multiple agents,
219
- // depending on which agent was found first.
220
- //
221
- // We explicitly do not have support for running tasks
222
- // inside of a sub agent at the moment, so we can be sure
223
- // that any sub agents are not the agent we're looking for.
224
- var taskAgentID uuid.NullUUID
225
- var taskAgentLifecycle * codersdk.WorkspaceAgentLifecycle
226
- var taskAgentHealth * codersdk.WorkspaceAgentHealth
227
- for _ , resource := range ws .LatestBuild .Resources {
228
- for _ , agent := range resource .Agents {
229
- if agent .ParentID .Valid {
230
- continue
231
- }
232
-
233
- taskAgentID = uuid.NullUUID {Valid : true , UUID : agent .ID }
234
- taskAgentLifecycle = & agent .LifecycleState
235
- taskAgentHealth = & agent .Health
236
- break
237
- }
238
- }
239
-
240
- var currentState * codersdk.TaskStateEntry
241
- if ws .LatestAppStatus != nil {
242
- currentState = & codersdk.TaskStateEntry {
243
- Timestamp : ws .LatestAppStatus .CreatedAt ,
244
- State : codersdk .TaskState (ws .LatestAppStatus .State ),
245
- Message : ws .LatestAppStatus .Message ,
246
- URI : ws .LatestAppStatus .URI ,
247
- }
248
- }
249
-
250
- tasks = append (tasks , codersdk.Task {
251
- ID : ws .ID ,
252
- OrganizationID : ws .OrganizationID ,
253
- OwnerID : ws .OwnerID ,
254
- OwnerName : ws .OwnerName ,
255
- Name : ws .Name ,
256
- TemplateID : ws .TemplateID ,
257
- TemplateName : ws .TemplateName ,
258
- TemplateDisplayName : ws .TemplateDisplayName ,
259
- TemplateIcon : ws .TemplateIcon ,
260
- WorkspaceID : uuid.NullUUID {Valid : true , UUID : ws .ID },
261
- WorkspaceAgentID : taskAgentID ,
262
- WorkspaceAgentLifecycle : taskAgentLifecycle ,
263
- WorkspaceAgentHealth : taskAgentHealth ,
264
- CreatedAt : ws .CreatedAt ,
265
- UpdatedAt : ws .UpdatedAt ,
266
- InitialPrompt : promptsByBuildID [ws .LatestBuild .ID ],
267
- Status : ws .LatestBuild .Status ,
268
- CurrentState : currentState ,
269
- })
281
+ tasks = append (tasks , taskFromWorkspace (ws , promptsByBuildID [ws .LatestBuild .ID ]))
270
282
}
271
283
272
284
return tasks , nil
@@ -464,3 +476,78 @@ func (api *API) taskGet(rw http.ResponseWriter, r *http.Request) {
464
476
465
477
httpapi .Write (ctx , rw , http .StatusOK , tasks [0 ])
466
478
}
479
+
480
+ // taskDelete is an experimental endpoint to delete a task by ID (workspace ID).
481
+ // It creates a delete workspace build and returns 202 Accepted if the build was
482
+ // created.
483
+ func (api * API ) taskDelete (rw http.ResponseWriter , r * http.Request ) {
484
+ ctx := r .Context ()
485
+ apiKey := httpmw .APIKey (r )
486
+
487
+ idStr := chi .URLParam (r , "id" )
488
+ taskID , err := uuid .Parse (idStr )
489
+ if err != nil {
490
+ httpapi .Write (ctx , rw , http .StatusBadRequest , codersdk.Response {
491
+ Message : fmt .Sprintf ("Invalid UUID %q for task ID." , idStr ),
492
+ })
493
+ return
494
+ }
495
+
496
+ // For now, taskID = workspaceID, once we have a task data model in
497
+ // the DB, we can change this lookup.
498
+ workspaceID := taskID
499
+ workspace , err := api .Database .GetWorkspaceByID (ctx , workspaceID )
500
+ if httpapi .Is404Error (err ) {
501
+ httpapi .ResourceNotFound (rw )
502
+ return
503
+ }
504
+ if err != nil {
505
+ httpapi .Write (ctx , rw , http .StatusInternalServerError , codersdk.Response {
506
+ Message : "Internal error fetching workspace." ,
507
+ Detail : err .Error (),
508
+ })
509
+ return
510
+ }
511
+
512
+ data , err := api .workspaceData (ctx , []database.Workspace {workspace })
513
+ if err != nil {
514
+ httpapi .Write (ctx , rw , http .StatusInternalServerError , codersdk.Response {
515
+ Message : "Internal error fetching workspace resources." ,
516
+ Detail : err .Error (),
517
+ })
518
+ return
519
+ }
520
+ if len (data .builds ) == 0 || len (data .templates ) == 0 {
521
+ httpapi .ResourceNotFound (rw )
522
+ return
523
+ }
524
+ if data .builds [0 ].HasAITask == nil || ! * data .builds [0 ].HasAITask {
525
+ httpapi .ResourceNotFound (rw )
526
+ return
527
+ }
528
+
529
+ // Construct a request to the workspace build creation handler to
530
+ // initiate deletion.
531
+ buildReq := codersdk.CreateWorkspaceBuildRequest {
532
+ Transition : codersdk .WorkspaceTransitionDelete ,
533
+ Reason : "Deleted via tasks API" ,
534
+ }
535
+
536
+ _ , err = api .postWorkspaceBuildsInternal (
537
+ ctx ,
538
+ apiKey ,
539
+ workspace ,
540
+ buildReq ,
541
+ func (action policy.Action , object rbac.Objecter ) bool {
542
+ return api .Authorize (r , action , object )
543
+ },
544
+ audit .WorkspaceBuildBaggageFromRequest (r ),
545
+ )
546
+ if err != nil {
547
+ httperror .WriteWorkspaceBuildError (ctx , rw , err )
548
+ return
549
+ }
550
+
551
+ // Delete build created successfully.
552
+ rw .WriteHeader (http .StatusAccepted )
553
+ }
0 commit comments