Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9e789dc

Browse files
fix!: use devcontainer ID when rebuilding a devcontainer
This PR replaces the use of the **container** ID with the **devcontainer** ID. This is a breaking change. This allows rebuilding a devcontainer when there is no valid container ID.
1 parent fb0e7a2 commit 9e789dc

File tree

11 files changed

+146
-156
lines changed

11 files changed

+146
-156
lines changed

agent/agentcontainers/api.go

Lines changed: 18 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -494,8 +494,8 @@ func (api *API) Routes() http.Handler {
494494
r.Get("/", api.handleList)
495495
// TODO(mafredri): Simplify this route as the previous /devcontainers
496496
// /-route was dropped. We can drop the /devcontainers prefix here too.
497-
r.Route("/devcontainers", func(r chi.Router) {
498-
r.Post("/container/{container}/recreate", api.handleDevcontainerRecreate)
497+
r.Route("/devcontainers/{devcontainer}", func(r chi.Router) {
498+
r.Post("/recreate", api.handleDevcontainerRecreate)
499499
})
500500

501501
return r
@@ -859,68 +859,42 @@ func (api *API) getContainers() (codersdk.WorkspaceAgentListContainersResponse,
859859
// devcontainer by referencing the container.
860860
func (api *API) handleDevcontainerRecreate(w http.ResponseWriter, r *http.Request) {
861861
ctx := r.Context()
862-
containerID := chi.URLParam(r, "container")
862+
devcontainerID := chi.URLParam(r, "devcontainer")
863863

864-
if containerID == "" {
864+
if devcontainerID == "" {
865865
httpapi.Write(ctx, w, http.StatusBadRequest, codersdk.Response{
866-
Message: "Missing container ID or name",
867-
Detail: "Container ID or name is required to recreate a devcontainer.",
866+
Message: "Missing devcontainer ID",
867+
Detail: "Devcontainer ID is required to recreate a devcontainer.",
868868
})
869869
return
870870
}
871871

872-
containers, err := api.getContainers()
873-
if err != nil {
874-
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
875-
Message: "Could not list containers",
876-
Detail: err.Error(),
877-
})
878-
return
879-
}
880-
881-
containerIdx := slices.IndexFunc(containers.Containers, func(c codersdk.WorkspaceAgentContainer) bool { return c.Match(containerID) })
882-
if containerIdx == -1 {
883-
httpapi.Write(ctx, w, http.StatusNotFound, codersdk.Response{
884-
Message: "Container not found",
885-
Detail: "Container ID or name not found in the list of containers.",
886-
})
887-
return
888-
}
889-
890-
container := containers.Containers[containerIdx]
891-
workspaceFolder := container.Labels[DevcontainerLocalFolderLabel]
892-
configPath := container.Labels[DevcontainerConfigFileLabel]
872+
api.mu.Lock()
893873

894-
// Workspace folder is required to recreate a container, we don't verify
895-
// the config path here because it's optional.
896-
if workspaceFolder == "" {
897-
httpapi.Write(ctx, w, http.StatusBadRequest, codersdk.Response{
898-
Message: "Missing workspace folder label",
899-
Detail: "The container is not a devcontainer, the container must have the workspace folder label to support recreation.",
900-
})
901-
return
874+
var workspaceFolder string
875+
for _, dc := range api.knownDevcontainers {
876+
if dc.ID.String() == devcontainerID {
877+
workspaceFolder = dc.WorkspaceFolder
878+
break
879+
}
902880
}
903881

904-
api.mu.Lock()
905-
906882
dc, ok := api.knownDevcontainers[workspaceFolder]
907883
switch {
908884
case !ok:
909885
api.mu.Unlock()
910886

911-
// This case should not happen if the container is a valid devcontainer.
912-
api.logger.Error(ctx, "devcontainer not found for workspace folder", slog.F("workspace_folder", workspaceFolder))
913-
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
887+
httpapi.Write(ctx, w, http.StatusNotFound, codersdk.Response{
914888
Message: "Devcontainer not found.",
915-
Detail: fmt.Sprintf("Could not find devcontainer for workspace folder: %q", workspaceFolder),
889+
Detail: fmt.Sprintf("Could not find devcontainer with ID: %q", devcontainerID),
916890
})
917891
return
918892
case dc.Status == codersdk.WorkspaceAgentDevcontainerStatusStarting:
919893
api.mu.Unlock()
920894

921895
httpapi.Write(ctx, w, http.StatusConflict, codersdk.Response{
922896
Message: "Devcontainer recreation already in progress",
923-
Detail: fmt.Sprintf("Recreation for workspace folder %q is already underway.", dc.WorkspaceFolder),
897+
Detail: fmt.Sprintf("Recreation for devcontainer %q is already underway.", dc.Name),
924898
})
925899
return
926900
}
@@ -931,14 +905,14 @@ func (api *API) handleDevcontainerRecreate(w http.ResponseWriter, r *http.Reques
931905
dc.Container = nil
932906
api.knownDevcontainers[dc.WorkspaceFolder] = dc
933907
go func() {
934-
_ = api.CreateDevcontainer(dc.WorkspaceFolder, configPath, WithRemoveExistingContainer())
908+
_ = api.CreateDevcontainer(dc.WorkspaceFolder, dc.ConfigPath, WithRemoveExistingContainer())
935909
}()
936910

937911
api.mu.Unlock()
938912

939913
httpapi.Write(ctx, w, http.StatusAccepted, codersdk.Response{
940914
Message: "Devcontainer recreation initiated",
941-
Detail: fmt.Sprintf("Recreation process for workspace folder %q has started.", dc.WorkspaceFolder),
915+
Detail: fmt.Sprintf("Recreation process for devcontainer %q has started.", dc.Name),
942916
})
943917
}
944918

agent/agentcontainers/api_test.go

Lines changed: 63 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -493,78 +493,77 @@ func TestAPI(t *testing.T) {
493493
t.Run("Recreate", func(t *testing.T) {
494494
t.Parallel()
495495

496-
validContainer := codersdk.WorkspaceAgentContainer{
497-
ID: "container-id",
498-
FriendlyName: "container-name",
496+
devcontainerID1 := uuid.New()
497+
devcontainerID2 := uuid.New()
498+
workspaceFolder1 := "/workspace/test1"
499+
workspaceFolder2 := "/workspace/test2"
500+
configPath1 := "/workspace/test1/.devcontainer/devcontainer.json"
501+
configPath2 := "/workspace/test2/.devcontainer/devcontainer.json"
502+
503+
// Create a container that represents an existing devcontainer
504+
devContainer1 := codersdk.WorkspaceAgentContainer{
505+
ID: "container-1",
506+
FriendlyName: "test-container-1",
499507
Running: true,
500508
Labels: map[string]string{
501-
agentcontainers.DevcontainerLocalFolderLabel: "/workspaces",
502-
agentcontainers.DevcontainerConfigFileLabel: "/workspace/.devcontainer/devcontainer.json",
509+
agentcontainers.DevcontainerLocalFolderLabel: workspaceFolder1,
510+
agentcontainers.DevcontainerConfigFileLabel: configPath1,
503511
},
504512
}
505513

506-
missingFolderContainer := codersdk.WorkspaceAgentContainer{
507-
ID: "missing-folder-container",
508-
FriendlyName: "missing-folder-container",
509-
Labels: map[string]string{},
514+
devContainer2 := codersdk.WorkspaceAgentContainer{
515+
ID: "container-2",
516+
FriendlyName: "test-container-2",
517+
Running: true,
518+
Labels: map[string]string{
519+
agentcontainers.DevcontainerLocalFolderLabel: workspaceFolder2,
520+
agentcontainers.DevcontainerConfigFileLabel: configPath2,
521+
},
510522
}
511523

512524
tests := []struct {
513-
name string
514-
containerID string
515-
lister *fakeContainerCLI
516-
devcontainerCLI *fakeDevcontainerCLI
517-
wantStatus []int
518-
wantBody []string
525+
name string
526+
devcontainerID string
527+
setupDevcontainers []codersdk.WorkspaceAgentDevcontainer
528+
lister *fakeContainerCLI
529+
devcontainerCLI *fakeDevcontainerCLI
530+
wantStatus []int
531+
wantBody []string
519532
}{
520533
{
521-
name: "Missing container ID",
522-
containerID: "",
534+
name: "Missing devcontainer ID",
535+
devcontainerID: "",
523536
lister: &fakeContainerCLI{},
524537
devcontainerCLI: &fakeDevcontainerCLI{},
525538
wantStatus: []int{http.StatusBadRequest},
526-
wantBody: []string{"Missing container ID or name"},
539+
wantBody: []string{"Missing devcontainer ID"},
527540
},
528541
{
529-
name: "List error",
530-
containerID: "container-id",
542+
name: "Devcontainer not found",
543+
devcontainerID: uuid.NewString(),
531544
lister: &fakeContainerCLI{
532-
listErr: xerrors.New("list error"),
533-
},
534-
devcontainerCLI: &fakeDevcontainerCLI{},
535-
wantStatus: []int{http.StatusInternalServerError},
536-
wantBody: []string{"Could not list containers"},
537-
},
538-
{
539-
name: "Container not found",
540-
containerID: "nonexistent-container",
541-
lister: &fakeContainerCLI{
542-
containers: codersdk.WorkspaceAgentListContainersResponse{
543-
Containers: []codersdk.WorkspaceAgentContainer{validContainer},
544-
},
545+
arch: "<none>", // Unsupported architecture, don't inject subagent.
545546
},
546547
devcontainerCLI: &fakeDevcontainerCLI{},
547548
wantStatus: []int{http.StatusNotFound},
548-
wantBody: []string{"Container not found"},
549+
wantBody: []string{"Devcontainer not found"},
549550
},
550551
{
551-
name: "Missing workspace folder label",
552-
containerID: "missing-folder-container",
553-
lister: &fakeContainerCLI{
554-
containers: codersdk.WorkspaceAgentListContainersResponse{
555-
Containers: []codersdk.WorkspaceAgentContainer{missingFolderContainer},
552+
name: "Devcontainer CLI error",
553+
devcontainerID: devcontainerID1.String(),
554+
setupDevcontainers: []codersdk.WorkspaceAgentDevcontainer{
555+
{
556+
ID: devcontainerID1,
557+
Name: "test-devcontainer-1",
558+
WorkspaceFolder: workspaceFolder1,
559+
ConfigPath: configPath1,
560+
Status: codersdk.WorkspaceAgentDevcontainerStatusRunning,
561+
Container: &devContainer1,
556562
},
557563
},
558-
devcontainerCLI: &fakeDevcontainerCLI{},
559-
wantStatus: []int{http.StatusBadRequest},
560-
wantBody: []string{"Missing workspace folder label"},
561-
},
562-
{
563-
name: "Devcontainer CLI error",
564-
containerID: "container-id",
565564
lister: &fakeContainerCLI{
566565
containers: codersdk.WorkspaceAgentListContainersResponse{
567-
Containers: []codersdk.WorkspaceAgentContainer{validContainer},
566+
Containers: []codersdk.WorkspaceAgentContainer{devContainer1},
568567
},
569568
arch: "<none>", // Unsupported architecture, don't inject subagent.
570569
},
@@ -575,11 +574,21 @@ func TestAPI(t *testing.T) {
575574
wantBody: []string{"Devcontainer recreation initiated", "Devcontainer recreation already in progress"},
576575
},
577576
{
578-
name: "OK",
579-
containerID: "container-id",
577+
name: "OK",
578+
devcontainerID: devcontainerID2.String(),
579+
setupDevcontainers: []codersdk.WorkspaceAgentDevcontainer{
580+
{
581+
ID: devcontainerID2,
582+
Name: "test-devcontainer-2",
583+
WorkspaceFolder: workspaceFolder2,
584+
ConfigPath: configPath2,
585+
Status: codersdk.WorkspaceAgentDevcontainerStatusRunning,
586+
Container: &devContainer2,
587+
},
588+
},
580589
lister: &fakeContainerCLI{
581590
containers: codersdk.WorkspaceAgentListContainersResponse{
582-
Containers: []codersdk.WorkspaceAgentContainer{validContainer},
591+
Containers: []codersdk.WorkspaceAgentContainer{devContainer2},
583592
},
584593
arch: "<none>", // Unsupported architecture, don't inject subagent.
585594
},
@@ -608,13 +617,16 @@ func TestAPI(t *testing.T) {
608617

609618
// Setup router with the handler under test.
610619
r := chi.NewRouter()
620+
611621
api := agentcontainers.NewAPI(
612622
logger,
613623
agentcontainers.WithClock(mClock),
614624
agentcontainers.WithContainerCLI(tt.lister),
615625
agentcontainers.WithDevcontainerCLI(tt.devcontainerCLI),
616626
agentcontainers.WithWatcher(watcher.NewNoop()),
627+
agentcontainers.WithDevcontainers(tt.setupDevcontainers, nil),
617628
)
629+
618630
api.Init()
619631
defer api.Close()
620632
r.Mount("/", api.Routes())
@@ -626,7 +638,7 @@ func TestAPI(t *testing.T) {
626638

627639
for i := range tt.wantStatus {
628640
// Simulate HTTP request to the recreate endpoint.
629-
req := httptest.NewRequest(http.MethodPost, "/devcontainers/container/"+tt.containerID+"/recreate", nil).
641+
req := httptest.NewRequest(http.MethodPost, "/devcontainers/"+tt.devcontainerID+"/recreate", nil).
630642
WithContext(ctx)
631643
rec := httptest.NewRecorder()
632644
r.ServeHTTP(rec, req)

coderd/apidoc/docs.go

Lines changed: 3 additions & 3 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: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/coderd.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1314,7 +1314,7 @@ func New(options *Options) *API {
13141314
r.Get("/listening-ports", api.workspaceAgentListeningPorts)
13151315
r.Get("/connection", api.workspaceAgentConnection)
13161316
r.Get("/containers", api.workspaceAgentListContainers)
1317-
r.Post("/containers/devcontainers/container/{container}/recreate", api.workspaceAgentRecreateDevcontainer)
1317+
r.Post("/containers/devcontainers/{devcontainer}/recreate", api.workspaceAgentRecreateDevcontainer)
13181318
r.Get("/coordinate", api.workspaceAgentClientCoordinate)
13191319

13201320
// PTY is part of workspaceAppServer.

coderd/workspaceagents.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -905,19 +905,19 @@ func (api *API) workspaceAgentListContainers(rw http.ResponseWriter, r *http.Req
905905
// @Tags Agents
906906
// @Produce json
907907
// @Param workspaceagent path string true "Workspace agent ID" format(uuid)
908-
// @Param container path string true "Container ID or name"
908+
// @Param devcontainer path string true "Devcontainer ID"
909909
// @Success 202 {object} codersdk.Response
910-
// @Router /workspaceagents/{workspaceagent}/containers/devcontainers/container/{container}/recreate [post]
910+
// @Router /workspaceagents/{workspaceagent}/containers/devcontainers/{devcontainer}/recreate [post]
911911
func (api *API) workspaceAgentRecreateDevcontainer(rw http.ResponseWriter, r *http.Request) {
912912
ctx := r.Context()
913913
workspaceAgent := httpmw.WorkspaceAgentParam(r)
914914

915-
container := chi.URLParam(r, "container")
916-
if container == "" {
915+
devcontainer := chi.URLParam(r, "devcontainer")
916+
if devcontainer == "" {
917917
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
918-
Message: "Container ID or name is required.",
918+
Message: "Devcontainer ID is required.",
919919
Validations: []codersdk.ValidationError{
920-
{Field: "container", Detail: "Container ID or name is required."},
920+
{Field: "devcontainer", Detail: "Devcontainer ID is required."},
921921
},
922922
})
923923
return
@@ -961,7 +961,7 @@ func (api *API) workspaceAgentRecreateDevcontainer(rw http.ResponseWriter, r *ht
961961
}
962962
defer release()
963963

964-
m, err := agentConn.RecreateDevcontainer(ctx, container)
964+
m, err := agentConn.RecreateDevcontainer(ctx, devcontainer)
965965
if err != nil {
966966
if errors.Is(err, context.Canceled) {
967967
httpapi.Write(ctx, rw, http.StatusRequestTimeout, codersdk.Response{

0 commit comments

Comments
 (0)