@@ -36,7 +36,7 @@ import (
36
36
// @Param user path string true "Template version ID" format(uuid)
37
37
// @Param templateversion path string true "Template version ID" format(uuid)
38
38
// @Success 101
39
- // @Router /users/{user}/ templateversions/{templateversion}/parameters [get]
39
+ // @Router /templateversions/{templateversion}/dynamic- parameters [get]
40
40
func (api * API )templateVersionDynamicParameters (rw http.ResponseWriter ,r * http.Request ) {
41
41
ctx := r .Context ()
42
42
templateVersion := httpmw .TemplateVersionParam (r )
@@ -77,12 +77,12 @@ func (api *API) templateVersionDynamicParameters(rw http.ResponseWriter, r *http
77
77
}
78
78
}
79
79
80
- type previewFunction func (ctx context.Context ,values map [string ]string ) (* preview.Output , hcl.Diagnostics )
80
+ type previewFunction func (ctx context.Context ,ownerID uuid. UUID , values map [string ]string ) (* preview.Output , hcl.Diagnostics )
81
81
82
82
func (api * API )handleDynamicParameters (rw http.ResponseWriter ,r * http.Request ,tf database.TemplateVersionTerraformValue ,templateVersion database.TemplateVersion ) {
83
83
var (
84
- ctx = r .Context ()
85
- user = httpmw .UserParam (r )
84
+ ctx = r .Context ()
85
+ apikey = httpmw .APIKey (r )
86
86
)
87
87
88
88
// nolint:gocritic // We need to fetch the templates files for the Terraform
@@ -130,7 +130,7 @@ func (api *API) handleDynamicParameters(rw http.ResponseWriter, r *http.Request,
130
130
templateFS = files .NewOverlayFS (templateFS , []files.Overlay {{Path :".terraform/modules" ,FS :moduleFilesFS }})
131
131
}
132
132
133
- owner ,err := getWorkspaceOwnerData (ctx ,api .Database ,user ,templateVersion .OrganizationID )
133
+ owner ,err := getWorkspaceOwnerData (ctx ,api .Database ,apikey . UserID ,templateVersion .OrganizationID )
134
134
if err != nil {
135
135
httpapi .Write (ctx ,rw ,http .StatusInternalServerError , codersdk.Response {
136
136
Message :"Internal error fetching workspace owner." ,
@@ -145,10 +145,46 @@ func (api *API) handleDynamicParameters(rw http.ResponseWriter, r *http.Request,
145
145
Owner :owner ,
146
146
}
147
147
148
- api .handleParameterWebsocket (rw ,r ,func (ctx context.Context ,values map [string ]string ) (* preview.Output , hcl.Diagnostics ) {
148
+ // failedOwners keeps track of which owners failed to fetch from the database.
149
+ // This prevents db spam on repeated requests for the same failed owner.
150
+ failedOwners := make (map [uuid.UUID ]error )
151
+ failedOwnerDiag := hcl.Diagnostics {
152
+ {
153
+ Severity :hcl .DiagError ,
154
+ Summary :"Failed to fetch workspace owner" ,
155
+ Detail :"Please check your permissions or the user may not exist." ,
156
+ Extra : previewtypes.DiagnosticExtra {
157
+ Code :"owner_not_found" ,
158
+ },
159
+ },
160
+ }
161
+
162
+ api .handleParameterWebsocket (rw ,r ,apikey .UserID ,func (ctx context.Context ,ownerID uuid.UUID ,values map [string ]string ) (* preview.Output , hcl.Diagnostics ) {
163
+ if ownerID == uuid .Nil {
164
+ // Default to the authenticated user
165
+ // Nice for testing
166
+ ownerID = apikey .UserID
167
+ }
168
+
169
+ if _ ,ok := failedOwners [ownerID ];ok {
170
+ // If it has failed once, assume it will fail always.
171
+ // Re-open the websocket to try again.
172
+ return nil ,failedOwnerDiag
173
+ }
174
+
149
175
// Update the input values with the new values.
150
- // The rest of the input is unchanged.
151
176
input .ParameterValues = values
177
+
178
+ // Update the owner if there is a change
179
+ if input .Owner .ID != ownerID .String () {
180
+ owner ,err = getWorkspaceOwnerData (ctx ,api .Database ,ownerID ,templateVersion .OrganizationID )
181
+ if err != nil {
182
+ failedOwners [ownerID ]= err
183
+ return nil ,failedOwnerDiag
184
+ }
185
+ input .Owner = owner
186
+ }
187
+
152
188
return preview .Preview (ctx ,input ,templateFS )
153
189
})
154
190
}
@@ -239,7 +275,7 @@ func (api *API) handleStaticParameters(rw http.ResponseWriter, r *http.Request,
239
275
params = append (params ,param )
240
276
}
241
277
242
- api .handleParameterWebsocket (rw ,r ,func (_ context.Context ,values map [string ]string ) (* preview.Output , hcl.Diagnostics ) {
278
+ api .handleParameterWebsocket (rw ,r ,uuid . Nil , func (_ context.Context , _ uuid. UUID ,values map [string ]string ) (* preview.Output , hcl.Diagnostics ) {
243
279
for i := range params {
244
280
param := & params [i ]
245
281
paramValue ,ok := values [param .Name ]
@@ -264,7 +300,7 @@ func (api *API) handleStaticParameters(rw http.ResponseWriter, r *http.Request,
264
300
})
265
301
}
266
302
267
- func (api * API )handleParameterWebsocket (rw http.ResponseWriter ,r * http.Request ,render previewFunction ) {
303
+ func (api * API )handleParameterWebsocket (rw http.ResponseWriter ,r * http.Request ,ownerID uuid. UUID , render previewFunction ) {
268
304
ctx ,cancel := context .WithTimeout (r .Context (),30 * time .Minute )
269
305
defer cancel ()
270
306
@@ -284,7 +320,7 @@ func (api *API) handleParameterWebsocket(rw http.ResponseWriter, r *http.Request
284
320
)
285
321
286
322
// Send an initial form state, computed without any user input.
287
- result ,diagnostics := render (ctx ,map [string ]string {})
323
+ result ,diagnostics := render (ctx ,ownerID , map [string ]string {})
288
324
response := codersdk.DynamicParametersResponse {
289
325
ID :- 1 ,// Always start with -1.
290
326
Diagnostics :db2sdk .HCLDiagnostics (diagnostics ),
@@ -312,7 +348,7 @@ func (api *API) handleParameterWebsocket(rw http.ResponseWriter, r *http.Request
312
348
return
313
349
}
314
350
315
- result ,diagnostics := render (ctx ,update .Inputs )
351
+ result ,diagnostics := render (ctx ,update .OwnerID , update . Inputs )
316
352
response := codersdk.DynamicParametersResponse {
317
353
ID :update .ID ,
318
354
Diagnostics :db2sdk .HCLDiagnostics (diagnostics ),
@@ -332,17 +368,24 @@ func (api *API) handleParameterWebsocket(rw http.ResponseWriter, r *http.Request
332
368
func getWorkspaceOwnerData (
333
369
ctx context.Context ,
334
370
db database.Store ,
335
- user database. User ,
371
+ ownerID uuid. UUID ,
336
372
organizationID uuid.UUID ,
337
373
) (previewtypes.WorkspaceOwner ,error ) {
338
374
var g errgroup.Group
339
375
376
+ // TODO: @emyrk we should only need read access on the org member, not the
377
+ // site wide user object. Figure out a better way to handle this.
378
+ user ,err := db .GetUserByID (ctx ,ownerID )
379
+ if err != nil {
380
+ return previewtypes.WorkspaceOwner {},xerrors .Errorf ("fetch user: %w" ,err )
381
+ }
382
+
340
383
var ownerRoles []previewtypes.WorkspaceOwnerRBACRole
341
384
g .Go (func ()error {
342
385
// nolint:gocritic // This is kind of the wrong query to use here, but it
343
386
// matches how the provisioner currently works. We should figure out
344
387
// something that needs less escalation but has the correct behavior.
345
- row ,err := db .GetAuthorizationUserRoles (dbauthz .AsSystemRestricted (ctx ),user . ID )
388
+ row ,err := db .GetAuthorizationUserRoles (dbauthz .AsSystemRestricted (ctx ),ownerID )
346
389
if err != nil {
347
390
return err
348
391
}
@@ -372,7 +415,7 @@ func getWorkspaceOwnerData(
372
415
// The correct public key has to be sent. This will not be leaked
373
416
// unless the template leaks it.
374
417
// nolint:gocritic
375
- key ,err := db .GetGitSSHKey (dbauthz .AsSystemRestricted (ctx ),user . ID )
418
+ key ,err := db .GetGitSSHKey (dbauthz .AsSystemRestricted (ctx ),ownerID )
376
419
if err != nil {
377
420
return err
378
421
}
@@ -388,7 +431,7 @@ func getWorkspaceOwnerData(
388
431
// nolint:gocritic
389
432
groups ,err := db .GetGroups (dbauthz .AsSystemRestricted (ctx ), database.GetGroupsParams {
390
433
OrganizationID :organizationID ,
391
- HasMemberID :user . ID ,
434
+ HasMemberID :ownerID ,
392
435
})
393
436
if err != nil {
394
437
return err
@@ -400,7 +443,7 @@ func getWorkspaceOwnerData(
400
443
return nil
401
444
})
402
445
403
- err : =g .Wait ()
446
+ err = g .Wait ()
404
447
if err != nil {
405
448
return previewtypes.WorkspaceOwner {},err
406
449
}