@@ -16,13 +16,12 @@ import (
16
16
"github.com/coder/coder/httpmw"
17
17
)
18
18
19
- // Workspace is the JSON representation of a Coder workspace.
20
- // This type matches the database object for now, but is
21
- // abstract for ease of change later on.
19
+ // Workspace is a per-user deployment of a project. It tracks
20
+ // project versions, and can be updated.
22
21
type Workspace database.Workspace
23
22
24
- // WorkspaceHistory is the JSON representation of a workspace transitioning
25
- // from state-to-state .
23
+ // WorkspaceHistory is an at-point representation of a workspace state.
24
+ // Iterate on before/after to determine a chronological history .
26
25
type WorkspaceHistory struct {
27
26
ID uuid.UUID `json:"id"`
28
27
CreatedAt time.Time `json:"created_at"`
@@ -36,13 +35,14 @@ type WorkspaceHistory struct {
36
35
Initiator string `json:"initiator"`
37
36
}
38
37
39
- // CreateWorkspaceRequest enables callers to create a new Workspace .
38
+ // CreateWorkspaceRequest provides options for creating a new workspace .
40
39
type CreateWorkspaceRequest struct {
41
- Name string `json:"name" validate:"username,required"`
40
+ ProjectID uuid.UUID `json:"project_id" validate:"required"`
41
+ Name string `json:"name" validate:"username,required"`
42
42
}
43
43
44
- // CreateWorkspaceBuildRequest enables callers to create a new workspace build .
45
- type CreateWorkspaceBuildRequest struct {
44
+ // CreateWorkspaceHistoryRequest provides options to update the latest workspace history .
45
+ type CreateWorkspaceHistoryRequest struct {
46
46
ProjectHistoryID uuid.UUID `json:"project_history_id" validate:"required"`
47
47
Transition database.WorkspaceTransition `json:"transition" validate:"oneof=create start stop delete,required"`
48
48
}
@@ -51,8 +51,8 @@ type workspaces struct {
51
51
Database database.Store
52
52
}
53
53
54
- // allWorkspaces lists all workspaces for the currently authenticated user .
55
- func (w * workspaces ) allWorkspaces (rw http.ResponseWriter , r * http.Request ) {
54
+ // Returns all workspaces across all projects and organizations .
55
+ func (w * workspaces ) listAllWorkspaces (rw http.ResponseWriter , r * http.Request ) {
56
56
apiKey := httpmw .APIKey (r )
57
57
workspaces , err := w .Database .GetWorkspacesByUserID (r .Context (), apiKey .UserID )
58
58
if errors .Is (err , sql .ErrNoRows ) {
@@ -73,7 +73,7 @@ func (w *workspaces) allWorkspaces(rw http.ResponseWriter, r *http.Request) {
73
73
render .JSON (rw , r , apiWorkspaces )
74
74
}
75
75
76
- // allWorkspacesForProject lists all projects for the parameterized project.
76
+ // Returns all workspaces for a specific project.
77
77
func (w * workspaces ) allWorkspacesForProject (rw http.ResponseWriter , r * http.Request ) {
78
78
apiKey := httpmw .APIKey (r )
79
79
project := httpmw .ProjectParam (r )
@@ -99,27 +99,61 @@ func (w *workspaces) allWorkspacesForProject(rw http.ResponseWriter, r *http.Req
99
99
render .JSON (rw , r , apiWorkspaces )
100
100
}
101
101
102
- // createWorkspace creates a new workspace for the currently authenticated user.
103
- func (w * workspaces ) createWorkspace (rw http.ResponseWriter , r * http.Request ) {
102
+ // Create a new workspace for the currently authenticated user.
103
+ func (w * workspaces ) createWorkspaceForUser (rw http.ResponseWriter , r * http.Request ) {
104
104
var createWorkspace CreateWorkspaceRequest
105
105
if ! httpapi .Read (rw , r , & createWorkspace ) {
106
106
return
107
107
}
108
108
apiKey := httpmw .APIKey (r )
109
- project := httpmw .ProjectParam (r )
109
+ project , err := w .Database .GetProjectByID (r .Context (), createWorkspace .ProjectID )
110
+ if errors .Is (err , sql .ErrNoRows ) {
111
+ httpapi .Write (rw , http .StatusBadRequest , httpapi.Response {
112
+ Message : fmt .Sprintf ("project %q doesn't exist" , createWorkspace .ProjectID .String ()),
113
+ Errors : []httpapi.Error {{
114
+ Field : "project_id" ,
115
+ Code : "not_found" ,
116
+ }},
117
+ })
118
+ return
119
+ }
120
+ if err != nil {
121
+ httpapi .Write (rw , http .StatusInternalServerError , httpapi.Response {
122
+ Message : fmt .Sprintf ("get project: %s" , err ),
123
+ })
124
+ return
125
+ }
126
+ _ , err = w .Database .GetOrganizationMemberByUserID (r .Context (), database.GetOrganizationMemberByUserIDParams {
127
+ OrganizationID : project .OrganizationID ,
128
+ UserID : apiKey .UserID ,
129
+ })
130
+ if errors .Is (err , sql .ErrNoRows ) {
131
+ httpapi .Write (rw , http .StatusUnauthorized , httpapi.Response {
132
+ Message : "you aren't allowed to access projects in that organization" ,
133
+ })
134
+ return
135
+ }
136
+ if err != nil {
137
+ httpapi .Write (rw , http .StatusInternalServerError , httpapi.Response {
138
+ Message : fmt .Sprintf ("get organization member: %s" , err ),
139
+ })
140
+ return
141
+ }
110
142
111
143
workspace , err := w .Database .GetWorkspaceByUserIDAndName (r .Context (), database.GetWorkspaceByUserIDAndNameParams {
112
144
OwnerID : apiKey .UserID ,
113
145
Name : createWorkspace .Name ,
114
146
})
115
147
if err == nil {
148
+ // If the workspace already exists, don't allow creation.
116
149
project , err := w .Database .GetProjectByID (r .Context (), workspace .ProjectID )
117
150
if err != nil {
118
151
httpapi .Write (rw , http .StatusInternalServerError , httpapi.Response {
119
152
Message : fmt .Sprintf ("find project for conflicting workspace name %q: %s" , createWorkspace .Name , err ),
120
153
})
121
154
return
122
155
}
156
+ // The project is fetched for clarity to the user on where the conflicting name may be.
123
157
httpapi .Write (rw , http .StatusConflict , httpapi.Response {
124
158
Message : fmt .Sprintf ("workspace %q already exists in the %q project" , createWorkspace .Name , project .Name ),
125
159
Errors : []httpapi.Error {{
@@ -136,6 +170,7 @@ func (w *workspaces) createWorkspace(rw http.ResponseWriter, r *http.Request) {
136
170
return
137
171
}
138
172
173
+ // Workspaces are created without any versions.
139
174
workspace , err = w .Database .InsertWorkspace (r .Context (), database.InsertWorkspaceParams {
140
175
ID : uuid .New (),
141
176
CreatedAt : database .Now (),
@@ -155,14 +190,16 @@ func (w *workspaces) createWorkspace(rw http.ResponseWriter, r *http.Request) {
155
190
render .JSON (rw , r , convertWorkspace (workspace ))
156
191
}
157
192
158
- func (* workspaces ) workspace (rw http.ResponseWriter , r * http.Request ) {
193
+ // Returns a single singleWorkspace.
194
+ func (* workspaces ) singleWorkspace (rw http.ResponseWriter , r * http.Request ) {
159
195
workspace := httpmw .WorkspaceParam (r )
160
196
161
197
render .Status (r , http .StatusOK )
162
198
render .JSON (rw , r , convertWorkspace (workspace ))
163
199
}
164
200
165
- func (w * workspaces ) allWorkspaceHistory (rw http.ResponseWriter , r * http.Request ) {
201
+ // Returns all workspace history. This is not sorted. Use before/after to chronologically sort.
202
+ func (w * workspaces ) listAllWorkspaceHistory (rw http.ResponseWriter , r * http.Request ) {
166
203
workspace := httpmw .WorkspaceParam (r )
167
204
168
205
histories , err := w .Database .GetWorkspaceHistoryByWorkspaceID (r .Context (), workspace .ID )
@@ -185,6 +222,7 @@ func (w *workspaces) allWorkspaceHistory(rw http.ResponseWriter, r *http.Request
185
222
render .JSON (rw , r , apiHistory )
186
223
}
187
224
225
+ // Returns the latest workspace history. This works by querying for history without "after" set.
188
226
func (w * workspaces ) latestWorkspaceHistory (rw http.ResponseWriter , r * http.Request ) {
189
227
workspace := httpmw .WorkspaceParam (r )
190
228
@@ -206,8 +244,10 @@ func (w *workspaces) latestWorkspaceHistory(rw http.ResponseWriter, r *http.Requ
206
244
render .JSON (rw , r , convertWorkspaceHistory (history ))
207
245
}
208
246
209
- func (w * workspaces ) createWorkspaceBuild (rw http.ResponseWriter , r * http.Request ) {
210
- var createBuild CreateWorkspaceBuildRequest
247
+ // Begins transitioning a workspace to new state. This queues a provision job to asyncronously
248
+ // update the underlying infrastructure. Only one historical transition can occur at a time.
249
+ func (w * workspaces ) createWorkspaceHistory (rw http.ResponseWriter , r * http.Request ) {
250
+ var createBuild CreateWorkspaceHistoryRequest
211
251
if ! httpapi .Read (rw , r , & createBuild ) {
212
252
return
213
253
}
@@ -255,6 +295,8 @@ func (w *workspaces) createWorkspaceBuild(rw http.ResponseWriter, r *http.Reques
255
295
}
256
296
257
297
var workspaceHistory database.WorkspaceHistory
298
+ // This must happen in a transaction to ensure history can be inserted, and
299
+ // the prior history can update it's "after" column to point at the new.
258
300
err = w .Database .InTx (func (db database.Store ) error {
259
301
workspaceHistory , err = db .InsertWorkspaceHistory (r .Context (), database.InsertWorkspaceHistoryParams {
260
302
ID : uuid .New (),
@@ -273,11 +315,10 @@ func (w *workspaces) createWorkspaceBuild(rw http.ResponseWriter, r *http.Reques
273
315
}
274
316
275
317
if priorHistoryID .Valid {
318
+ // Update the prior history entries "after" column.
276
319
err = db .UpdateWorkspaceHistoryByID (r .Context (), database.UpdateWorkspaceHistoryByIDParams {
277
- ID : priorHistory .ID ,
278
- UpdatedAt : database .Now (),
279
- ProvisionerState : priorHistory .ProvisionerState ,
280
- CompletedAt : priorHistory .CompletedAt ,
320
+ ID : priorHistory .ID ,
321
+ UpdatedAt : database .Now (),
281
322
AfterID : uuid.NullUUID {
282
323
UUID : workspaceHistory .ID ,
283
324
Valid : true ,
@@ -301,11 +342,12 @@ func (w *workspaces) createWorkspaceBuild(rw http.ResponseWriter, r *http.Reques
301
342
render .JSON (rw , r , convertWorkspaceHistory (workspaceHistory ))
302
343
}
303
344
304
- // convertWorkspace consumes the database representation and outputs an API friendly representation .
345
+ // Converts the internal workspace representation to a public external-facing model .
305
346
func convertWorkspace (workspace database.Workspace ) Workspace {
306
347
return Workspace (workspace )
307
348
}
308
349
350
+ // Converts the internal history representation to a public external-facing model.
309
351
func convertWorkspaceHistory (workspaceHistory database.WorkspaceHistory ) WorkspaceHistory {
310
352
//nolint:unconvert
311
353
return WorkspaceHistory (WorkspaceHistory {
0 commit comments