@@ -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 {