@@ -2,6 +2,7 @@ package coderd
2
2
3
3
import (
4
4
"database/sql"
5
+ "encoding/json"
5
6
"errors"
6
7
"fmt"
7
8
"net/http"
@@ -22,13 +23,13 @@ type WorkspaceHistory struct {
22
23
ID uuid.UUID `json:"id"`
23
24
CreatedAt time.Time `json:"created_at"`
24
25
UpdatedAt time.Time `json:"updated_at"`
25
- CompletedAt time.Time `json:"completed_at"`
26
26
WorkspaceID uuid.UUID `json:"workspace_id"`
27
27
ProjectHistoryID uuid.UUID `json:"project_history_id"`
28
28
BeforeID uuid.UUID `json:"before_id"`
29
29
AfterID uuid.UUID `json:"after_id"`
30
30
Transition database.WorkspaceTransition `json:"transition"`
31
31
Initiator string `json:"initiator"`
32
+ Job ProvisionerJob `json:"job"`
32
33
}
33
34
34
35
// CreateWorkspaceHistoryRequest provides options to update the latest workspace history.
@@ -37,8 +38,6 @@ type CreateWorkspaceHistoryRequest struct {
37
38
Transition database.WorkspaceTransition `json:"transition" validate:"oneof=create start stop delete,required"`
38
39
}
39
40
40
- // Begins transitioning a workspace to new state. This queues a provision job to asynchronously
41
- // update the underlying infrastructure. Only one historical transition can occur at a time.
42
41
func (api * api ) postWorkspaceHistoryByUser (rw http.ResponseWriter , r * http.Request ) {
43
42
var createBuild CreateWorkspaceHistoryRequest
44
43
if ! httpapi .Read (rw , r , & createBuild ) {
@@ -63,16 +62,28 @@ func (api *api) postWorkspaceHistoryByUser(rw http.ResponseWriter, r *http.Reque
63
62
})
64
63
return
65
64
}
65
+ project , err := api .Database .GetProjectByID (r .Context (), projectHistory .ProjectID )
66
+ if err != nil {
67
+ httpapi .Write (rw , http .StatusInternalServerError , httpapi.Response {
68
+ Message : fmt .Sprintf ("get project: %s" , err ),
69
+ })
70
+ return
71
+ }
66
72
67
73
// Store prior history ID if it exists to update it after we create new!
68
74
priorHistoryID := uuid.NullUUID {}
69
75
priorHistory , err := api .Database .GetWorkspaceHistoryByWorkspaceIDWithoutAfter (r .Context (), workspace .ID )
70
76
if err == nil {
71
- if ! priorHistory .CompletedAt .Valid {
72
- httpapi .Write (rw , http .StatusConflict , httpapi.Response {
73
- Message : "a workspace build is already active" ,
74
- })
75
- return
77
+ priorJob , err := api .Database .GetProvisionerJobByID (r .Context (), priorHistory .ProvisionJobID )
78
+ if err == nil {
79
+ convertedJob := convertProvisionerJob (priorJob )
80
+ if convertedJob .Status == ProvisionerJobStatusPending ||
81
+ convertedJob .Status == ProvisionerJobStatusRunning {
82
+ httpapi .Write (rw , http .StatusConflict , httpapi.Response {
83
+ Message : "a workspace build is already active" ,
84
+ })
85
+ return
86
+ }
76
87
}
77
88
78
89
priorHistoryID = uuid.NullUUID {
@@ -87,10 +98,34 @@ func (api *api) postWorkspaceHistoryByUser(rw http.ResponseWriter, r *http.Reque
87
98
return
88
99
}
89
100
101
+ var provisionerJob database.ProvisionerJob
90
102
var workspaceHistory database.WorkspaceHistory
91
103
// This must happen in a transaction to ensure history can be inserted, and
92
104
// the prior history can update it's "after" column to point at the new.
93
105
err = api .Database .InTx (func (db database.Store ) error {
106
+ // Generate the ID before-hand so the provisioner job is aware of it!
107
+ workspaceHistoryID := uuid .New ()
108
+ input , err := json .Marshal (workspaceProvisionJob {
109
+ WorkspaceHistoryID : workspaceHistoryID ,
110
+ })
111
+ if err != nil {
112
+ return xerrors .Errorf ("marshal provision job: %w" , err )
113
+ }
114
+
115
+ provisionerJob , err = db .InsertProvisionerJob (r .Context (), database.InsertProvisionerJobParams {
116
+ ID : uuid .New (),
117
+ CreatedAt : database .Now (),
118
+ UpdatedAt : database .Now (),
119
+ InitiatorID : user .ID ,
120
+ Provisioner : project .Provisioner ,
121
+ Type : database .ProvisionerJobTypeWorkspaceProvision ,
122
+ ProjectID : project .ID ,
123
+ Input : input ,
124
+ })
125
+ if err != nil {
126
+ return xerrors .Errorf ("insert provisioner job: %w" , err )
127
+ }
128
+
94
129
workspaceHistory , err = db .InsertWorkspaceHistory (r .Context (), database.InsertWorkspaceHistoryParams {
95
130
ID : uuid .New (),
96
131
CreatedAt : database .Now (),
@@ -100,8 +135,7 @@ func (api *api) postWorkspaceHistoryByUser(rw http.ResponseWriter, r *http.Reque
100
135
BeforeID : priorHistoryID ,
101
136
Initiator : user .ID ,
102
137
Transition : createBuild .Transition ,
103
- // This should create a provision job once that gets implemented!
104
- ProvisionJobID : uuid .New (),
138
+ ProvisionJobID : provisionerJob .ID ,
105
139
})
106
140
if err != nil {
107
141
return xerrors .Errorf ("insert workspace history: %w" , err )
@@ -132,7 +166,7 @@ func (api *api) postWorkspaceHistoryByUser(rw http.ResponseWriter, r *http.Reque
132
166
}
133
167
134
168
render .Status (r , http .StatusCreated )
135
- render .JSON (rw , r , convertWorkspaceHistory (workspaceHistory ))
169
+ render .JSON (rw , r , convertWorkspaceHistory (workspaceHistory , provisionerJob ))
136
170
}
137
171
138
172
// Returns all workspace history. This is not sorted. Use before/after to chronologically sort.
@@ -152,7 +186,14 @@ func (api *api) workspaceHistoryByUser(rw http.ResponseWriter, r *http.Request)
152
186
153
187
apiHistory := make ([]WorkspaceHistory , 0 , len (histories ))
154
188
for _ , history := range histories {
155
- apiHistory = append (apiHistory , convertWorkspaceHistory (history ))
189
+ job , err := api .Database .GetProvisionerJobByID (r .Context (), history .ProvisionJobID )
190
+ if err != nil {
191
+ httpapi .Write (rw , http .StatusInternalServerError , httpapi.Response {
192
+ Message : fmt .Sprintf ("get provisioner job: %s" , err ),
193
+ })
194
+ return
195
+ }
196
+ apiHistory = append (apiHistory , convertWorkspaceHistory (history , job ))
156
197
}
157
198
158
199
render .Status (r , http .StatusOK )
@@ -176,9 +217,33 @@ func (api *api) latestWorkspaceHistoryByUser(rw http.ResponseWriter, r *http.Req
176
217
})
177
218
return
178
219
}
220
+ job , err := api .Database .GetProvisionerJobByID (r .Context (), history .ProvisionJobID )
221
+ if err != nil {
222
+ httpapi .Write (rw , http .StatusInternalServerError , httpapi.Response {
223
+ Message : fmt .Sprintf ("get provisioner job: %s" , err ),
224
+ })
225
+ return
226
+ }
179
227
180
228
render .Status (r , http .StatusOK )
181
- render .JSON (rw , r , convertWorkspaceHistory (history ))
229
+ render .JSON (rw , r , convertWorkspaceHistory (history , job ))
230
+ }
231
+
232
+ // Converts the internal history representation to a public external-facing model.
233
+ func convertWorkspaceHistory (workspaceHistory database.WorkspaceHistory , provisionerJob database.ProvisionerJob ) WorkspaceHistory {
234
+ //nolint:unconvert
235
+ return WorkspaceHistory (WorkspaceHistory {
236
+ ID : workspaceHistory .ID ,
237
+ CreatedAt : workspaceHistory .CreatedAt ,
238
+ UpdatedAt : workspaceHistory .UpdatedAt ,
239
+ WorkspaceID : workspaceHistory .WorkspaceID ,
240
+ ProjectHistoryID : workspaceHistory .ProjectHistoryID ,
241
+ BeforeID : workspaceHistory .BeforeID .UUID ,
242
+ AfterID : workspaceHistory .AfterID .UUID ,
243
+ Transition : workspaceHistory .Transition ,
244
+ Initiator : workspaceHistory .Initiator ,
245
+ Job : convertProvisionerJob (provisionerJob ),
246
+ })
182
247
}
183
248
184
249
func workspaceHistoryLogsChannel (workspaceHistoryID uuid.UUID ) string {
0 commit comments