mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
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.
This commit is contained in:
6
coderd/apidoc/docs.go
generated
6
coderd/apidoc/docs.go
generated
@ -8453,7 +8453,7 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspaceagents/{workspaceagent}/containers/devcontainers/container/{container}/recreate": {
|
||||
"/workspaceagents/{workspaceagent}/containers/devcontainers/{devcontainer}/recreate": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
@ -8479,8 +8479,8 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Container ID or name",
|
||||
"name": "container",
|
||||
"description": "Devcontainer ID",
|
||||
"name": "devcontainer",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
|
6
coderd/apidoc/swagger.json
generated
6
coderd/apidoc/swagger.json
generated
@ -7472,7 +7472,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/workspaceagents/{workspaceagent}/containers/devcontainers/container/{container}/recreate": {
|
||||
"/workspaceagents/{workspaceagent}/containers/devcontainers/{devcontainer}/recreate": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
@ -7494,8 +7494,8 @@
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Container ID or name",
|
||||
"name": "container",
|
||||
"description": "Devcontainer ID",
|
||||
"name": "devcontainer",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
|
@ -1314,7 +1314,7 @@ func New(options *Options) *API {
|
||||
r.Get("/listening-ports", api.workspaceAgentListeningPorts)
|
||||
r.Get("/connection", api.workspaceAgentConnection)
|
||||
r.Get("/containers", api.workspaceAgentListContainers)
|
||||
r.Post("/containers/devcontainers/container/{container}/recreate", api.workspaceAgentRecreateDevcontainer)
|
||||
r.Post("/containers/devcontainers/{devcontainer}/recreate", api.workspaceAgentRecreateDevcontainer)
|
||||
r.Get("/coordinate", api.workspaceAgentClientCoordinate)
|
||||
|
||||
// PTY is part of workspaceAppServer.
|
||||
|
@ -905,19 +905,19 @@ func (api *API) workspaceAgentListContainers(rw http.ResponseWriter, r *http.Req
|
||||
// @Tags Agents
|
||||
// @Produce json
|
||||
// @Param workspaceagent path string true "Workspace agent ID" format(uuid)
|
||||
// @Param container path string true "Container ID or name"
|
||||
// @Param devcontainer path string true "Devcontainer ID"
|
||||
// @Success 202 {object} codersdk.Response
|
||||
// @Router /workspaceagents/{workspaceagent}/containers/devcontainers/container/{container}/recreate [post]
|
||||
// @Router /workspaceagents/{workspaceagent}/containers/devcontainers/{devcontainer}/recreate [post]
|
||||
func (api *API) workspaceAgentRecreateDevcontainer(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
workspaceAgent := httpmw.WorkspaceAgentParam(r)
|
||||
|
||||
container := chi.URLParam(r, "container")
|
||||
if container == "" {
|
||||
devcontainer := chi.URLParam(r, "devcontainer")
|
||||
if devcontainer == "" {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Container ID or name is required.",
|
||||
Message: "Devcontainer ID is required.",
|
||||
Validations: []codersdk.ValidationError{
|
||||
{Field: "container", Detail: "Container ID or name is required."},
|
||||
{Field: "devcontainer", Detail: "Devcontainer ID is required."},
|
||||
},
|
||||
})
|
||||
return
|
||||
@ -961,7 +961,7 @@ func (api *API) workspaceAgentRecreateDevcontainer(rw http.ResponseWriter, r *ht
|
||||
}
|
||||
defer release()
|
||||
|
||||
m, err := agentConn.RecreateDevcontainer(ctx, container)
|
||||
m, err := agentConn.RecreateDevcontainer(ctx, devcontainer)
|
||||
if err != nil {
|
||||
if errors.Is(err, context.Canceled) {
|
||||
httpapi.Write(ctx, rw, http.StatusRequestTimeout, codersdk.Response{
|
||||
|
@ -1396,36 +1396,42 @@ func TestWorkspaceAgentRecreateDevcontainer(t *testing.T) {
|
||||
var (
|
||||
workspaceFolder = t.TempDir()
|
||||
configFile = filepath.Join(workspaceFolder, ".devcontainer", "devcontainer.json")
|
||||
dcLabels = map[string]string{
|
||||
agentcontainers.DevcontainerLocalFolderLabel: workspaceFolder,
|
||||
agentcontainers.DevcontainerConfigFileLabel: configFile,
|
||||
}
|
||||
devcontainerID = uuid.New()
|
||||
|
||||
// Create a container that would be associated with the devcontainer
|
||||
devContainer = codersdk.WorkspaceAgentContainer{
|
||||
ID: uuid.NewString(),
|
||||
CreatedAt: dbtime.Now(),
|
||||
FriendlyName: testutil.GetRandomName(t),
|
||||
Image: "busybox:latest",
|
||||
Labels: dcLabels,
|
||||
Running: true,
|
||||
Status: "running",
|
||||
Labels: map[string]string{
|
||||
agentcontainers.DevcontainerLocalFolderLabel: workspaceFolder,
|
||||
agentcontainers.DevcontainerConfigFileLabel: configFile,
|
||||
},
|
||||
Running: true,
|
||||
Status: "running",
|
||||
}
|
||||
plainContainer = codersdk.WorkspaceAgentContainer{
|
||||
ID: uuid.NewString(),
|
||||
CreatedAt: dbtime.Now(),
|
||||
FriendlyName: testutil.GetRandomName(t),
|
||||
Image: "busybox:latest",
|
||||
Labels: map[string]string{},
|
||||
Running: true,
|
||||
Status: "running",
|
||||
|
||||
devcontainer = codersdk.WorkspaceAgentDevcontainer{
|
||||
ID: devcontainerID,
|
||||
Name: "test-devcontainer",
|
||||
WorkspaceFolder: workspaceFolder,
|
||||
ConfigPath: configFile,
|
||||
Status: codersdk.WorkspaceAgentDevcontainerStatusRunning,
|
||||
Container: &devContainer,
|
||||
}
|
||||
)
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
setupMock func(mccli *acmock.MockContainerCLI, mdccli *acmock.MockDevcontainerCLI) (status int)
|
||||
name string
|
||||
devcontainerID string
|
||||
setupDevcontainers []codersdk.WorkspaceAgentDevcontainer
|
||||
setupMock func(mccli *acmock.MockContainerCLI, mdccli *acmock.MockDevcontainerCLI) (status int)
|
||||
}{
|
||||
{
|
||||
name: "Recreate",
|
||||
name: "Recreate",
|
||||
devcontainerID: devcontainerID.String(),
|
||||
setupDevcontainers: []codersdk.WorkspaceAgentDevcontainer{devcontainer},
|
||||
setupMock: func(mccli *acmock.MockContainerCLI, mdccli *acmock.MockDevcontainerCLI) int {
|
||||
mccli.EXPECT().List(gomock.Any()).Return(codersdk.WorkspaceAgentListContainersResponse{
|
||||
Containers: []codersdk.WorkspaceAgentContainer{devContainer},
|
||||
@ -1438,21 +1444,14 @@ func TestWorkspaceAgentRecreateDevcontainer(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Container does not exist",
|
||||
name: "Devcontainer does not exist",
|
||||
devcontainerID: uuid.NewString(),
|
||||
setupDevcontainers: nil,
|
||||
setupMock: func(mccli *acmock.MockContainerCLI, mdccli *acmock.MockDevcontainerCLI) int {
|
||||
mccli.EXPECT().List(gomock.Any()).Return(codersdk.WorkspaceAgentListContainersResponse{}, nil).AnyTimes()
|
||||
return http.StatusNotFound
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Not a devcontainer",
|
||||
setupMock: func(mccli *acmock.MockContainerCLI, mdccli *acmock.MockDevcontainerCLI) int {
|
||||
mccli.EXPECT().List(gomock.Any()).Return(codersdk.WorkspaceAgentListContainersResponse{
|
||||
Containers: []codersdk.WorkspaceAgentContainer{plainContainer},
|
||||
}, nil).AnyTimes()
|
||||
return http.StatusNotFound
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
@ -1472,16 +1471,21 @@ func TestWorkspaceAgentRecreateDevcontainer(t *testing.T) {
|
||||
}).WithAgent(func(agents []*proto.Agent) []*proto.Agent {
|
||||
return agents
|
||||
}).Do()
|
||||
|
||||
devcontainerAPIOptions := []agentcontainers.Option{
|
||||
agentcontainers.WithContainerCLI(mccli),
|
||||
agentcontainers.WithDevcontainerCLI(mdccli),
|
||||
agentcontainers.WithWatcher(watcher.NewNoop()),
|
||||
}
|
||||
if tc.setupDevcontainers != nil {
|
||||
devcontainerAPIOptions = append(devcontainerAPIOptions,
|
||||
agentcontainers.WithDevcontainers(tc.setupDevcontainers, nil))
|
||||
}
|
||||
|
||||
_ = agenttest.New(t, client.URL, r.AgentToken, func(o *agent.Options) {
|
||||
o.Logger = logger.Named("agent")
|
||||
o.Devcontainers = true
|
||||
o.DevcontainerAPIOptions = append(
|
||||
o.DevcontainerAPIOptions,
|
||||
agentcontainers.WithContainerCLI(mccli),
|
||||
agentcontainers.WithDevcontainerCLI(mdccli),
|
||||
agentcontainers.WithWatcher(watcher.NewNoop()),
|
||||
agentcontainers.WithContainerLabelIncludeFilter(agentcontainers.DevcontainerLocalFolderLabel, workspaceFolder),
|
||||
)
|
||||
o.DevcontainerAPIOptions = devcontainerAPIOptions
|
||||
})
|
||||
resources := coderdtest.NewWorkspaceAgentWaiter(t, client, r.Workspace.ID).Wait()
|
||||
require.Len(t, resources, 1, "expected one resource")
|
||||
@ -1490,7 +1494,7 @@ func TestWorkspaceAgentRecreateDevcontainer(t *testing.T) {
|
||||
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
|
||||
_, err := client.WorkspaceAgentRecreateDevcontainer(ctx, agentID, devContainer.ID)
|
||||
_, err := client.WorkspaceAgentRecreateDevcontainer(ctx, agentID, tc.devcontainerID)
|
||||
if wantStatus > 0 {
|
||||
cerr, ok := codersdk.AsError(err)
|
||||
require.True(t, ok, "expected error to be a coder error")
|
||||
|
Reference in New Issue
Block a user