9
9
10
10
"golang.org/x/xerrors"
11
11
12
+ "github.com/go-chi/chi/v5"
13
+
12
14
"github.com/coder/coder/v2/coderd/httpapi"
13
15
"github.com/coder/coder/v2/codersdk"
14
16
"github.com/coder/quartz"
@@ -20,9 +22,10 @@ const (
20
22
getContainersTimeout = 5 * time .Second
21
23
)
22
24
23
- type devcontainersHandler struct {
25
+ type Handler struct {
24
26
cacheDuration time.Duration
25
27
cl Lister
28
+ dccli DevcontainerCLI
26
29
clock quartz.Clock
27
30
28
31
// lockCh protects the below fields. We use a channel instead of a mutex so we
@@ -32,20 +35,26 @@ type devcontainersHandler struct {
32
35
mtime time.Time
33
36
}
34
37
35
- // Option is a functional option for devcontainersHandler .
36
- type Option func (* devcontainersHandler )
38
+ // Option is a functional option for Handler .
39
+ type Option func (* Handler )
37
40
38
41
// WithLister sets the agentcontainers.Lister implementation to use.
39
42
// The default implementation uses the Docker CLI to list containers.
40
43
func WithLister (cl Lister ) Option {
41
- return func (ch * devcontainersHandler ) {
44
+ return func (ch * Handler ) {
42
45
ch .cl = cl
43
46
}
44
47
}
45
48
46
- // New returns a new devcontainersHandler with the given options applied.
47
- func New (options ... Option ) http.Handler {
48
- ch := & devcontainersHandler {
49
+ func WithDevcontainerCLI (dccli DevcontainerCLI ) Option {
50
+ return func (ch * Handler ) {
51
+ ch .dccli = dccli
52
+ }
53
+ }
54
+
55
+ // New returns a new Handler with the given options applied.
56
+ func New (options ... Option ) * Handler {
57
+ ch := & Handler {
49
58
lockCh : make (chan struct {}, 1 ),
50
59
}
51
60
for _ , opt := range options {
@@ -54,7 +63,7 @@ func New(options ...Option) http.Handler {
54
63
return ch
55
64
}
56
65
57
- func (ch * devcontainersHandler ) ServeHTTP (rw http.ResponseWriter , r * http.Request ) {
66
+ func (ch * Handler ) List (rw http.ResponseWriter , r * http.Request ) {
58
67
select {
59
68
case <- r .Context ().Done ():
60
69
// Client went away.
@@ -80,7 +89,7 @@ func (ch *devcontainersHandler) ServeHTTP(rw http.ResponseWriter, r *http.Reques
80
89
}
81
90
}
82
91
83
- func (ch * devcontainersHandler ) getContainers (ctx context.Context ) (codersdk.WorkspaceAgentListContainersResponse , error ) {
92
+ func (ch * Handler ) getContainers (ctx context.Context ) (codersdk.WorkspaceAgentListContainersResponse , error ) {
84
93
select {
85
94
case <- ctx .Done ():
86
95
return codersdk.WorkspaceAgentListContainersResponse {}, ctx .Err ()
@@ -149,3 +158,61 @@ var _ Lister = NoopLister{}
149
158
func (NoopLister ) List (_ context.Context ) (codersdk.WorkspaceAgentListContainersResponse , error ) {
150
159
return codersdk.WorkspaceAgentListContainersResponse {}, nil
151
160
}
161
+
162
+ func (ch * Handler ) Recreate (w http.ResponseWriter , r * http.Request ) {
163
+ ctx := r .Context ()
164
+ id := chi .URLParam (r , "id" )
165
+
166
+ if id == "" {
167
+ httpapi .Write (ctx , w , http .StatusBadRequest , codersdk.Response {
168
+ Message : "Missing container ID or name" ,
169
+ Detail : "Container ID or name is required to recreate a devcontainer." ,
170
+ })
171
+ return
172
+ }
173
+
174
+ containers , err := ch .cl .List (ctx )
175
+ if err != nil {
176
+ httpapi .Write (ctx , w , http .StatusInternalServerError , codersdk.Response {
177
+ Message : "Could not list containers" ,
178
+ Detail : err .Error (),
179
+ })
180
+ return
181
+ }
182
+
183
+ containerIdx := slices .IndexFunc (containers .Containers , func (c codersdk.WorkspaceAgentContainer ) bool {
184
+ return c .Match (id )
185
+ })
186
+ if containerIdx == - 1 {
187
+ httpapi .Write (ctx , w , http .StatusNotFound , codersdk.Response {
188
+ Message : "Container not found" ,
189
+ Detail : "Container ID or name not found in the list of containers." ,
190
+ })
191
+ return
192
+ }
193
+
194
+ container := containers .Containers [containerIdx ]
195
+ workspaceFolder := container .Labels [DevcontainerLocalFolderLabel ]
196
+ configPath := container .Labels [DevcontainerConfigFileLabel ]
197
+
198
+ // Workspace folder is required to recreate a container, we don't verify
199
+ // the config path here because it's optional.
200
+ if workspaceFolder == "" {
201
+ httpapi .Write (ctx , w , http .StatusBadRequest , codersdk.Response {
202
+ Message : "Missing workspace folder label" ,
203
+ Detail : "The workspace folder label is required to recreate a devcontainer." ,
204
+ })
205
+ return
206
+ }
207
+
208
+ _ , err = ch .dccli .Up (ctx , workspaceFolder , configPath , WithRemoveExistingContainer ())
209
+ if err != nil {
210
+ httpapi .Write (ctx , w , http .StatusInternalServerError , codersdk.Response {
211
+ Message : "Could not recreate devcontainer" ,
212
+ Detail : err .Error (),
213
+ })
214
+ return
215
+ }
216
+
217
+ w .WriteHeader (http .StatusNoContent )
218
+ }
0 commit comments