@@ -5,9 +5,11 @@ import (
5
5
"errors"
6
6
"fmt"
7
7
"net/http"
8
+ "time"
8
9
9
10
"github.com/go-chi/render"
10
11
"github.com/google/uuid"
12
+ "golang.org/x/xerrors"
11
13
12
14
"github.com/coder/coder/database"
13
15
"github.com/coder/coder/httpapi"
@@ -19,11 +21,32 @@ import (
19
21
// abstract for ease of change later on.
20
22
type Workspace database.Workspace
21
23
24
+ // WorkspaceHistory is the JSON representation of a workspace transitioning
25
+ // from state-to-state.
26
+ type WorkspaceHistory struct {
27
+ ID uuid.UUID `json:"id"`
28
+ CreatedAt time.Time `json:"created_at"`
29
+ UpdatedAt time.Time `json:"updated_at"`
30
+ CompletedAt time.Time `json:"completed_at"`
31
+ WorkspaceID uuid.UUID `json:"workspace_id"`
32
+ ProjectHistoryID uuid.UUID `json:"project_history_id"`
33
+ BeforeID uuid.UUID `json:"before_id"`
34
+ AfterID uuid.UUID `json:"after_id"`
35
+ Transition database.WorkspaceTransition `json:"transition"`
36
+ Initiator string `json:"initiator"`
37
+ }
38
+
22
39
// CreateWorkspaceRequest enables callers to create a new Workspace.
23
40
type CreateWorkspaceRequest struct {
24
41
Name string `json:"name" validate:"username,required"`
25
42
}
26
43
44
+ // CreateWorkspaceBuildRequest enables callers to create a new workspace build.
45
+ type CreateWorkspaceBuildRequest struct {
46
+ ProjectHistoryID uuid.UUID `json:"project_history_id" validate:"required"`
47
+ Transition database.WorkspaceTransition `json:"transition" validate:"oneof=create start stop delete,required"`
48
+ }
49
+
27
50
type workspaces struct {
28
51
Database database.Store
29
52
}
@@ -132,15 +155,169 @@ func (w *workspaces) createWorkspace(rw http.ResponseWriter, r *http.Request) {
132
155
render .JSON (rw , r , convertWorkspace (workspace ))
133
156
}
134
157
135
- // workspace returns a new workspace.
136
158
func (* workspaces ) workspace (rw http.ResponseWriter , r * http.Request ) {
137
159
workspace := httpmw .WorkspaceParam (r )
138
160
139
161
render .Status (r , http .StatusOK )
140
162
render .JSON (rw , r , convertWorkspace (workspace ))
141
163
}
142
164
165
+ func (w * workspaces ) allWorkspaceHistory (rw http.ResponseWriter , r * http.Request ) {
166
+ workspace := httpmw .WorkspaceParam (r )
167
+
168
+ histories , err := w .Database .GetWorkspaceHistoryByWorkspaceID (r .Context (), workspace .ID )
169
+ if errors .Is (err , sql .ErrNoRows ) {
170
+ err = nil
171
+ }
172
+ if err != nil {
173
+ httpapi .Write (rw , http .StatusInternalServerError , httpapi.Response {
174
+ Message : fmt .Sprintf ("get workspace history: %s" , err ),
175
+ })
176
+ return
177
+ }
178
+
179
+ apiHistory := make ([]WorkspaceHistory , 0 , len (histories ))
180
+ for _ , history := range histories {
181
+ apiHistory = append (apiHistory , convertWorkspaceHistory (history ))
182
+ }
183
+
184
+ render .Status (r , http .StatusOK )
185
+ render .JSON (rw , r , apiHistory )
186
+ }
187
+
188
+ func (w * workspaces ) latestWorkspaceHistory (rw http.ResponseWriter , r * http.Request ) {
189
+ workspace := httpmw .WorkspaceParam (r )
190
+
191
+ history , err := w .Database .GetWorkspaceHistoryByWorkspaceIDWithoutAfter (r .Context (), workspace .ID )
192
+ if errors .Is (err , sql .ErrNoRows ) {
193
+ httpapi .Write (rw , http .StatusNotFound , httpapi.Response {
194
+ Message : "workspace has no history" ,
195
+ })
196
+ return
197
+ }
198
+ if err != nil {
199
+ httpapi .Write (rw , http .StatusInternalServerError , httpapi.Response {
200
+ Message : fmt .Sprintf ("get workspace history: %s" , err ),
201
+ })
202
+ return
203
+ }
204
+
205
+ render .Status (r , http .StatusOK )
206
+ render .JSON (rw , r , convertWorkspaceHistory (history ))
207
+ }
208
+
209
+ func (w * workspaces ) createWorkspaceBuild (rw http.ResponseWriter , r * http.Request ) {
210
+ var createBuild CreateWorkspaceBuildRequest
211
+ if ! httpapi .Read (rw , r , & createBuild ) {
212
+ return
213
+ }
214
+ user := httpmw .UserParam (r )
215
+ workspace := httpmw .WorkspaceParam (r )
216
+ projectHistory , err := w .Database .GetProjectHistoryByID (r .Context (), createBuild .ProjectHistoryID )
217
+ if errors .Is (err , sql .ErrNoRows ) {
218
+ httpapi .Write (rw , http .StatusBadRequest , httpapi.Response {
219
+ Message : "project history not found" ,
220
+ Errors : []httpapi.Error {{
221
+ Field : "project_history_id" ,
222
+ Code : "exists" ,
223
+ }},
224
+ })
225
+ return
226
+ }
227
+ if err != nil {
228
+ httpapi .Write (rw , http .StatusInternalServerError , httpapi.Response {
229
+ Message : fmt .Sprintf ("get project history: %s" , err ),
230
+ })
231
+ return
232
+ }
233
+
234
+ // Store prior history ID if it exists to update it after we create new!
235
+ priorHistoryID := uuid.NullUUID {}
236
+ priorHistory , err := w .Database .GetWorkspaceHistoryByWorkspaceIDWithoutAfter (r .Context (), workspace .ID )
237
+ if err == nil {
238
+ if ! priorHistory .CompletedAt .Valid {
239
+ httpapi .Write (rw , http .StatusConflict , httpapi.Response {
240
+ Message : "a workspace build is already active" ,
241
+ })
242
+ return
243
+ }
244
+
245
+ priorHistoryID = uuid.NullUUID {
246
+ UUID : priorHistory .ID ,
247
+ Valid : true ,
248
+ }
249
+ }
250
+ if ! errors .Is (err , sql .ErrNoRows ) {
251
+ httpapi .Write (rw , http .StatusInternalServerError , httpapi.Response {
252
+ Message : fmt .Sprintf ("get prior workspace history: %s" , err ),
253
+ })
254
+ return
255
+ }
256
+
257
+ var workspaceHistory database.WorkspaceHistory
258
+ err = w .Database .InTx (func (db database.Store ) error {
259
+ workspaceHistory , err = db .InsertWorkspaceHistory (r .Context (), database.InsertWorkspaceHistoryParams {
260
+ ID : uuid .New (),
261
+ CreatedAt : database .Now (),
262
+ UpdatedAt : database .Now (),
263
+ WorkspaceID : workspace .ID ,
264
+ ProjectHistoryID : projectHistory .ID ,
265
+ BeforeID : priorHistoryID ,
266
+ Initiator : user .ID ,
267
+ Transition : createBuild .Transition ,
268
+ // This should create a provision job once that gets implemented!
269
+ ProvisionJobID : uuid .New (),
270
+ })
271
+ if err != nil {
272
+ return xerrors .Errorf ("insert workspace history: %w" , err )
273
+ }
274
+
275
+ if priorHistoryID .Valid {
276
+ err = db .UpdateWorkspaceHistoryByID (r .Context (), database.UpdateWorkspaceHistoryByIDParams {
277
+ ID : priorHistory .ID ,
278
+ UpdatedAt : database .Now (),
279
+ ProvisionerState : priorHistory .ProvisionerState ,
280
+ CompletedAt : priorHistory .CompletedAt ,
281
+ AfterID : uuid.NullUUID {
282
+ UUID : workspaceHistory .ID ,
283
+ Valid : true ,
284
+ },
285
+ })
286
+ if err != nil {
287
+ return xerrors .Errorf ("update prior workspace history: %w" , err )
288
+ }
289
+ }
290
+
291
+ return nil
292
+ })
293
+ if err != nil {
294
+ httpapi .Write (rw , http .StatusInternalServerError , httpapi.Response {
295
+ Message : err .Error (),
296
+ })
297
+ return
298
+ }
299
+
300
+ render .Status (r , http .StatusCreated )
301
+ render .JSON (rw , r , convertWorkspaceHistory (workspaceHistory ))
302
+ }
303
+
143
304
// convertWorkspace consumes the database representation and outputs an API friendly representation.
144
305
func convertWorkspace (workspace database.Workspace ) Workspace {
145
306
return Workspace (workspace )
146
307
}
308
+
309
+ func convertWorkspaceHistory (workspaceHistory database.WorkspaceHistory ) WorkspaceHistory {
310
+ //nolint:unconvert
311
+ return WorkspaceHistory (WorkspaceHistory {
312
+ ID : workspaceHistory .ID ,
313
+ CreatedAt : workspaceHistory .CreatedAt ,
314
+ UpdatedAt : workspaceHistory .UpdatedAt ,
315
+ CompletedAt : workspaceHistory .CompletedAt .Time ,
316
+ WorkspaceID : workspaceHistory .WorkspaceID ,
317
+ ProjectHistoryID : workspaceHistory .ProjectHistoryID ,
318
+ BeforeID : workspaceHistory .BeforeID .UUID ,
319
+ AfterID : workspaceHistory .AfterID .UUID ,
320
+ Transition : workspaceHistory .Transition ,
321
+ Initiator : workspaceHistory .Initiator ,
322
+ })
323
+ }
0 commit comments