@@ -214,8 +214,10 @@ func (api *API) Routes() http.Handler {
214
214
r := chi .NewRouter ()
215
215
216
216
r .Get ("/" , api .handleList )
217
- r .Get ("/devcontainers" , api .handleListDevcontainers )
218
- r .Post ("/{id}/recreate" , api .handleRecreate )
217
+ r .Route ("/devcontainers" , func (r chi.Router ) {
218
+ r .Get ("/" , api .handleDevcontainersList )
219
+ r .Post ("/container/{container}/recreate" , api .handleDevcontainerRecreate )
220
+ })
219
221
220
222
return r
221
223
}
@@ -376,12 +378,13 @@ func (api *API) getContainers(ctx context.Context) (codersdk.WorkspaceAgentListC
376
378
return copyListContainersResponse (api .containers ), nil
377
379
}
378
380
379
- // handleRecreate handles the HTTP request to recreate a container.
380
- func (api * API ) handleRecreate (w http.ResponseWriter , r * http.Request ) {
381
+ // handleDevcontainerRecreate handles the HTTP request to recreate a
382
+ // devcontainer by referencing the container.
383
+ func (api * API ) handleDevcontainerRecreate (w http.ResponseWriter , r * http.Request ) {
381
384
ctx := r .Context ()
382
- id := chi .URLParam (r , "id " )
385
+ containerID := chi .URLParam (r , "container " )
383
386
384
- if id == "" {
387
+ if containerID == "" {
385
388
httpapi .Write (ctx , w , http .StatusBadRequest , codersdk.Response {
386
389
Message : "Missing container ID or name" ,
387
390
Detail : "Container ID or name is required to recreate a devcontainer." ,
@@ -399,7 +402,7 @@ func (api *API) handleRecreate(w http.ResponseWriter, r *http.Request) {
399
402
}
400
403
401
404
containerIdx := slices .IndexFunc (containers .Containers , func (c codersdk.WorkspaceAgentContainer ) bool {
402
- return c .Match (id )
405
+ return c .Match (containerID )
403
406
})
404
407
if containerIdx == - 1 {
405
408
httpapi .Write (ctx , w , http .StatusNotFound , codersdk.Response {
@@ -418,7 +421,7 @@ func (api *API) handleRecreate(w http.ResponseWriter, r *http.Request) {
418
421
if workspaceFolder == "" {
419
422
httpapi .Write (ctx , w , http .StatusBadRequest , codersdk.Response {
420
423
Message : "Missing workspace folder label" ,
421
- Detail : "The workspace folder label is required to recreate a devcontainer ." ,
424
+ Detail : "The container is not a devcontainer, the container must have the workspace folder label to support recreation ." ,
422
425
})
423
426
return
424
427
}
@@ -434,32 +437,28 @@ func (api *API) handleRecreate(w http.ResponseWriter, r *http.Request) {
434
437
435
438
// TODO(mafredri): Temporarily handle clearing the dirty state after
436
439
// recreation, later on this should be handled by a "container watcher".
437
- select {
438
- case <- api .ctx .Done ():
439
- return
440
- case <- ctx .Done ():
441
- return
442
- case api .lockCh <- struct {}{}:
443
- defer func () { <- api .lockCh }()
444
- }
445
- for i := range api .knownDevcontainers {
446
- if api .knownDevcontainers [i ].WorkspaceFolder == workspaceFolder {
447
- if api .knownDevcontainers [i ].Dirty {
448
- api .logger .Info (ctx , "clearing dirty flag after recreation" ,
449
- slog .F ("workspace_folder" , workspaceFolder ),
450
- slog .F ("name" , api .knownDevcontainers [i ].Name ),
451
- )
452
- api .knownDevcontainers [i ].Dirty = false
440
+ if ! api .doLockedHandler (w , r , func () {
441
+ for i := range api .knownDevcontainers {
442
+ if api .knownDevcontainers [i ].WorkspaceFolder == workspaceFolder {
443
+ if api .knownDevcontainers [i ].Dirty {
444
+ api .logger .Info (ctx , "clearing dirty flag after recreation" ,
445
+ slog .F ("workspace_folder" , workspaceFolder ),
446
+ slog .F ("name" , api .knownDevcontainers [i ].Name ),
447
+ )
448
+ api .knownDevcontainers [i ].Dirty = false
449
+ }
450
+ return
453
451
}
454
- break
455
452
}
453
+ }) {
454
+ return
456
455
}
457
456
458
457
w .WriteHeader (http .StatusNoContent )
459
458
}
460
459
461
- // handleListDevcontainers handles the HTTP request to list known devcontainers.
462
- func (api * API ) handleListDevcontainers (w http.ResponseWriter , r * http.Request ) {
460
+ // handleDevcontainersList handles the HTTP request to list known devcontainers.
461
+ func (api * API ) handleDevcontainersList (w http.ResponseWriter , r * http.Request ) {
463
462
ctx := r .Context ()
464
463
465
464
// Run getContainers to detect the latest devcontainers and their state.
@@ -472,15 +471,12 @@ func (api *API) handleListDevcontainers(w http.ResponseWriter, r *http.Request)
472
471
return
473
472
}
474
473
475
- select {
476
- case <- api .ctx .Done ():
474
+ var devcontainers []codersdk.WorkspaceAgentDevcontainer
475
+ if ! api .doLockedHandler (w , r , func () {
476
+ devcontainers = slices .Clone (api .knownDevcontainers )
477
+ }) {
477
478
return
478
- case <- ctx .Done ():
479
- return
480
- case api .lockCh <- struct {}{}:
481
479
}
482
- devcontainers := slices .Clone (api .knownDevcontainers )
483
- <- api .lockCh
484
480
485
481
slices .SortFunc (devcontainers , func (a , b codersdk.WorkspaceAgentDevcontainer ) int {
486
482
if cmp := strings .Compare (a .WorkspaceFolder , b .WorkspaceFolder ); cmp != 0 {
@@ -499,34 +495,64 @@ func (api *API) handleListDevcontainers(w http.ResponseWriter, r *http.Request)
499
495
// markDevcontainerDirty finds the devcontainer with the given config file path
500
496
// and marks it as dirty. It acquires the lock before modifying the state.
501
497
func (api * API ) markDevcontainerDirty (configPath string , modifiedAt time.Time ) {
498
+ ok := api .doLocked (func () {
499
+ // Record the timestamp of when this configuration file was modified.
500
+ api .configFileModifiedTimes [configPath ] = modifiedAt
501
+
502
+ for i := range api .knownDevcontainers {
503
+ if api .knownDevcontainers [i ].ConfigPath != configPath {
504
+ continue
505
+ }
506
+
507
+ // TODO(mafredri): Simplistic mark for now, we should check if the
508
+ // container is running and if the config file was modified after
509
+ // the container was created.
510
+ if ! api .knownDevcontainers [i ].Dirty {
511
+ api .logger .Info (api .ctx , "marking devcontainer as dirty" ,
512
+ slog .F ("file" , configPath ),
513
+ slog .F ("name" , api .knownDevcontainers [i ].Name ),
514
+ slog .F ("workspace_folder" , api .knownDevcontainers [i ].WorkspaceFolder ),
515
+ slog .F ("modified_at" , modifiedAt ),
516
+ )
517
+ api .knownDevcontainers [i ].Dirty = true
518
+ }
519
+ }
520
+ })
521
+ if ! ok {
522
+ api .logger .Debug (api .ctx , "mark devcontainer dirty failed" , slog .F ("file" , configPath ))
523
+ }
524
+ }
525
+
526
+ func (api * API ) doLockedHandler (w http.ResponseWriter , r * http.Request , f func ()) bool {
502
527
select {
528
+ case <- r .Context ().Done ():
529
+ httpapi .Write (r .Context (), w , http .StatusRequestTimeout , codersdk.Response {
530
+ Message : "Request canceled" ,
531
+ Detail : "Request was canceled before we could process it." ,
532
+ })
533
+ return false
503
534
case <- api .ctx .Done ():
504
- return
535
+ httpapi .Write (r .Context (), w , http .StatusServiceUnavailable , codersdk.Response {
536
+ Message : "API closed" ,
537
+ Detail : "The API is closed and cannot process requests." ,
538
+ })
539
+ return false
505
540
case api .lockCh <- struct {}{}:
506
541
defer func () { <- api .lockCh }()
507
542
}
543
+ f ()
544
+ return true
545
+ }
508
546
509
- // Record the timestamp of when this configuration file was modified.
510
- api .configFileModifiedTimes [configPath ] = modifiedAt
511
-
512
- for i := range api .knownDevcontainers {
513
- if api .knownDevcontainers [i ].ConfigPath != configPath {
514
- continue
515
- }
516
-
517
- // TODO(mafredri): Simplistic mark for now, we should check if the
518
- // container is running and if the config file was modified after
519
- // the container was created.
520
- if ! api .knownDevcontainers [i ].Dirty {
521
- api .logger .Info (api .ctx , "marking devcontainer as dirty" ,
522
- slog .F ("file" , configPath ),
523
- slog .F ("name" , api .knownDevcontainers [i ].Name ),
524
- slog .F ("workspace_folder" , api .knownDevcontainers [i ].WorkspaceFolder ),
525
- slog .F ("modified_at" , modifiedAt ),
526
- )
527
- api .knownDevcontainers [i ].Dirty = true
528
- }
547
+ func (api * API ) doLocked (f func ()) bool {
548
+ select {
549
+ case <- api .ctx .Done ():
550
+ return false
551
+ case api .lockCh <- struct {}{}:
552
+ defer func () { <- api .lockCh }()
529
553
}
554
+ f ()
555
+ return true
530
556
}
531
557
532
558
func (api * API ) Close () error {
0 commit comments