@@ -36,7 +36,7 @@ import (
3636// @Param user path string true "Template version ID" format(uuid)
3737// @Param templateversion path string true "Template version ID" format(uuid)
3838// @Success 101
39- // @Router /users/{user}/ templateversions/{templateversion}/parameters [get]
39+ // @Router /templateversions/{templateversion}/dynamic- parameters [get]
4040func (api * API )templateVersionDynamicParameters (rw http.ResponseWriter ,r * http.Request ) {
4141ctx := r .Context ()
4242templateVersion := httpmw .TemplateVersionParam (r )
@@ -77,12 +77,12 @@ func (api *API) templateVersionDynamicParameters(rw http.ResponseWriter, r *http
7777}
7878}
7979
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 )
8181
8282func (api * API )handleDynamicParameters (rw http.ResponseWriter ,r * http.Request ,tf database.TemplateVersionTerraformValue ,templateVersion database.TemplateVersion ) {
8383var (
84- ctx = r .Context ()
85- user = httpmw .UserParam (r )
84+ ctx = r .Context ()
85+ apikey = httpmw .APIKey (r )
8686)
8787
8888// 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,
130130templateFS = files .NewOverlayFS (templateFS , []files.Overlay {{Path :".terraform/modules" ,FS :moduleFilesFS }})
131131}
132132
133- owner ,err := getWorkspaceOwnerData (ctx ,api .Database ,user ,templateVersion .OrganizationID )
133+ owner ,err := getWorkspaceOwnerData (ctx ,api .Database ,apikey . UserID ,templateVersion .OrganizationID )
134134if err != nil {
135135httpapi .Write (ctx ,rw ,http .StatusInternalServerError , codersdk.Response {
136136Message :"Internal error fetching workspace owner." ,
@@ -145,10 +145,46 @@ func (api *API) handleDynamicParameters(rw http.ResponseWriter, r *http.Request,
145145Owner :owner ,
146146}
147147
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+
149175// Update the input values with the new values.
150- // The rest of the input is unchanged.
151176input .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+
152188return preview .Preview (ctx ,input ,templateFS )
153189})
154190}
@@ -239,7 +275,7 @@ func (api *API) handleStaticParameters(rw http.ResponseWriter, r *http.Request,
239275params = append (params ,param )
240276}
241277
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 ) {
243279for i := range params {
244280param := & params [i ]
245281paramValue ,ok := values [param .Name ]
@@ -264,7 +300,7 @@ func (api *API) handleStaticParameters(rw http.ResponseWriter, r *http.Request,
264300})
265301}
266302
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 ) {
268304ctx ,cancel := context .WithTimeout (r .Context (),30 * time .Minute )
269305defer cancel ()
270306
@@ -284,7 +320,7 @@ func (api *API) handleParameterWebsocket(rw http.ResponseWriter, r *http.Request
284320)
285321
286322// 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 {})
288324response := codersdk.DynamicParametersResponse {
289325ID :- 1 ,// Always start with -1.
290326Diagnostics :db2sdk .HCLDiagnostics (diagnostics ),
@@ -312,7 +348,7 @@ func (api *API) handleParameterWebsocket(rw http.ResponseWriter, r *http.Request
312348return
313349}
314350
315- result ,diagnostics := render (ctx ,update .Inputs )
351+ result ,diagnostics := render (ctx ,update .OwnerID , update . Inputs )
316352response := codersdk.DynamicParametersResponse {
317353ID :update .ID ,
318354Diagnostics :db2sdk .HCLDiagnostics (diagnostics ),
@@ -332,17 +368,24 @@ func (api *API) handleParameterWebsocket(rw http.ResponseWriter, r *http.Request
332368func getWorkspaceOwnerData (
333369ctx context.Context ,
334370db database.Store ,
335- user database. User ,
371+ ownerID uuid. UUID ,
336372organizationID uuid.UUID ,
337373) (previewtypes.WorkspaceOwner ,error ) {
338374var g errgroup.Group
339375
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+
340383var ownerRoles []previewtypes.WorkspaceOwnerRBACRole
341384g .Go (func ()error {
342385// nolint:gocritic // This is kind of the wrong query to use here, but it
343386// matches how the provisioner currently works. We should figure out
344387// 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 )
346389if err != nil {
347390return err
348391}
@@ -372,7 +415,7 @@ func getWorkspaceOwnerData(
372415// The correct public key has to be sent. This will not be leaked
373416// unless the template leaks it.
374417// nolint:gocritic
375- key ,err := db .GetGitSSHKey (dbauthz .AsSystemRestricted (ctx ),user . ID )
418+ key ,err := db .GetGitSSHKey (dbauthz .AsSystemRestricted (ctx ),ownerID )
376419if err != nil {
377420return err
378421}
@@ -388,7 +431,7 @@ func getWorkspaceOwnerData(
388431// nolint:gocritic
389432groups ,err := db .GetGroups (dbauthz .AsSystemRestricted (ctx ), database.GetGroupsParams {
390433OrganizationID :organizationID ,
391- HasMemberID :user . ID ,
434+ HasMemberID :ownerID ,
392435})
393436if err != nil {
394437return err
@@ -400,7 +443,7 @@ func getWorkspaceOwnerData(
400443return nil
401444})
402445
403- err : =g .Wait ()
446+ err = g .Wait ()
404447if err != nil {
405448return previewtypes.WorkspaceOwner {},err
406449}