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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy