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

Skip to content

Commit f2d229e

Browse files
fix!: use devcontainer ID when rebuilding a devcontainer (#18604)
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 eca6381 commit f2d229e

File tree

11 files changed

+149
-161
lines changed

11 files changed

+149
-161
lines changed

agent/agentcontainers/api.go

Lines changed: 21 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -496,8 +496,8 @@ func (api *API) Routes() http.Handler {
496496
r.Get("/", api.handleList)
497497
// TODO(mafredri): Simplify this route as the previous /devcontainers
498498
// /-route was dropped. We can drop the /devcontainers prefix here too.
499-
r.Route("/devcontainers", func(r chi.Router) {
500-
r.Post("/container/{container}/recreate", api.handleDevcontainerRecreate)
499+
r.Route("/devcontainers/{devcontainer}", func(r chi.Router) {
500+
r.Post("/recreate", api.handleDevcontainerRecreate)
501501
})
502502

503503
return r
@@ -861,68 +861,40 @@ func (api *API) getContainers() (codersdk.WorkspaceAgentListContainersResponse,
861861
// devcontainer by referencing the container.
862862
func (api *API) handleDevcontainerRecreate(w http.ResponseWriter, r *http.Request) {
863863
ctx := r.Context()
864-
containerID := chi.URLParam(r, "container")
864+
devcontainerID := chi.URLParam(r, "devcontainer")
865865

866-
if containerID == "" {
866+
if devcontainerID == "" {
867867
httpapi.Write(ctx, w, http.StatusBadRequest, codersdk.Response{
868-
Message: "Missing container ID or name",
869-
Detail: "Container ID or name is required to recreate a devcontainer.",
870-
})
871-
return
872-
}
873-
874-
containers, err := api.getContainers()
875-
if err != nil {
876-
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
877-
Message: "Could not list containers",
878-
Detail: err.Error(),
879-
})
880-
return
881-
}
882-
883-
containerIdx := slices.IndexFunc(containers.Containers, func(c codersdk.WorkspaceAgentContainer) bool { return c.Match(containerID) })
884-
if containerIdx == -1 {
885-
httpapi.Write(ctx, w, http.StatusNotFound, codersdk.Response{
886-
Message: "Container not found",
887-
Detail: "Container ID or name not found in the list of containers.",
888-
})
889-
return
890-
}
891-
892-
container := containers.Containers[containerIdx]
893-
workspaceFolder := container.Labels[DevcontainerLocalFolderLabel]
894-
configPath := container.Labels[DevcontainerConfigFileLabel]
895-
896-
// Workspace folder is required to recreate a container, we don't verify
897-
// the config path here because it's optional.
898-
if workspaceFolder == "" {
899-
httpapi.Write(ctx, w, http.StatusBadRequest, codersdk.Response{
900-
Message: "Missing workspace folder label",
901-
Detail: "The container is not a devcontainer, the container must have the workspace folder label to support recreation.",
868+
Message: "Missing devcontainer ID",
869+
Detail: "Devcontainer ID is required to recreate a devcontainer.",
902870
})
903871
return
904872
}
905873

906874
api.mu.Lock()
907875

908-
dc, ok := api.knownDevcontainers[workspaceFolder]
909-
switch {
910-
case !ok:
876+
var dc codersdk.WorkspaceAgentDevcontainer
877+
for _, knownDC := range api.knownDevcontainers {
878+
if knownDC.ID.String() == devcontainerID {
879+
dc = knownDC
880+
break
881+
}
882+
}
883+
if dc.ID == uuid.Nil {
911884
api.mu.Unlock()
912885

913-
// This case should not happen if the container is a valid devcontainer.
914-
api.logger.Error(ctx, "devcontainer not found for workspace folder", slog.F("workspace_folder", workspaceFolder))
915-
httpapi.Write(ctx, w, http.StatusInternalServerError, codersdk.Response{
886+
httpapi.Write(ctx, w, http.StatusNotFound, codersdk.Response{
916887
Message: "Devcontainer not found.",
917-
Detail: fmt.Sprintf("Could not find devcontainer for workspace folder: %q", workspaceFolder),
888+
Detail: fmt.Sprintf("Could not find devcontainer with ID: %q", devcontainerID),
918889
})
919890
return
920-
case dc.Status == codersdk.WorkspaceAgentDevcontainerStatusStarting:
891+
}
892+
if dc.Status == codersdk.WorkspaceAgentDevcontainerStatusStarting {
921893
api.mu.Unlock()
922894

923895
httpapi.Write(ctx, w, http.StatusConflict, codersdk.Response{
924896
Message: "Devcontainer recreation already in progress",
925-
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),
926898
})
927899
return
928900
}
@@ -933,14 +905,14 @@ func (api *API) handleDevcontainerRecreate(w http.ResponseWriter, r *http.Reques
933905
dc.Container = nil
934906
api.knownDevcontainers[dc.WorkspaceFolder] = dc
935907
go func() {
936-
_ = api.CreateDevcontainer(dc.WorkspaceFolder, configPath, WithRemoveExistingContainer())
908+
_ = api.CreateDevcontainer(dc.WorkspaceFolder, dc.ConfigPath, WithRemoveExistingContainer())
937909
}()
938910

939911
api.mu.Unlock()
940912

941913
httpapi.Write(ctx, w, http.StatusAccepted, codersdk.Response{
942914
Message: "Devcontainer recreation initiated",
943-
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),
944916
})
945917
}
946918

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)