4
4
"context"
5
5
"database/sql"
6
6
"encoding/json"
7
- "io/fs"
8
7
"net/http"
9
8
"time"
10
9
@@ -19,8 +18,10 @@ import (
19
18
"github.com/coder/coder/v2/coderd/files"
20
19
"github.com/coder/coder/v2/coderd/httpapi"
21
20
"github.com/coder/coder/v2/coderd/httpmw"
21
+ "github.com/coder/coder/v2/coderd/util/ptr"
22
22
"github.com/coder/coder/v2/codersdk"
23
23
"github.com/coder/coder/v2/codersdk/wsjson"
24
+ sdkproto "github.com/coder/coder/v2/provisionersdk/proto"
24
25
"github.com/coder/preview"
25
26
previewtypes "github.com/coder/preview/types"
26
27
"github.com/coder/websocket"
@@ -60,11 +61,38 @@ func (api *API) templateVersionDynamicParameters(rw http.ResponseWriter, r *http
60
61
return
61
62
}
62
63
63
- render , closer , success := prepareDynamicPreview (ctx , rw , api .Database , api .FileCache , templateVersion , user )
64
- if ! success {
64
+ tf , err := api .Database .GetTemplateVersionTerraformValues (ctx , templateVersion .ID )
65
+ if err != nil && ! xerrors .Is (err , sql .ErrNoRows ) {
66
+ httpapi .Write (ctx , rw , http .StatusInternalServerError , codersdk.Response {
67
+ Message : "Failed to retrieve Terraform values for template version" ,
68
+ Detail : err .Error (),
69
+ })
65
70
return
66
71
}
67
- defer closer ()
72
+
73
+ staticDiagnostics := parameterProvisionerVersionDiagnostic (tf )
74
+
75
+ var render previewFunction
76
+ major , minor , err := apiversion .Parse (tf .ProvisionerdVersion )
77
+ if err != nil || major < 1 || (major == 1 && minor < 5 ) {
78
+ staticRender , err := prepareStaticPreview (ctx , api .Database , templateVersion .ID )
79
+ if err != nil {
80
+ httpapi .Write (ctx , rw , http .StatusInternalServerError , codersdk.Response {
81
+ Message : "Failed to setup static rendering" ,
82
+ Detail : err .Error (),
83
+ })
84
+ return
85
+ }
86
+ render = staticRender
87
+ } else {
88
+ // If the major version is 1.5+, we can use the dynamic preview
89
+ dynamicRender , closer , success := prepareDynamicPreview (ctx , rw , api .Database , api .FileCache , tf , templateVersion , user )
90
+ if ! success {
91
+ return
92
+ }
93
+ defer closer ()
94
+ render = dynamicRender
95
+ }
68
96
69
97
conn , err := websocket .Accept (rw , r , nil )
70
98
if err != nil {
@@ -128,7 +156,7 @@ func (api *API) templateVersionDynamicParameters(rw http.ResponseWriter, r *http
128
156
129
157
type previewFunction func (ctx context.Context , values map [string ]string ) (* preview.Output , hcl.Diagnostics )
130
158
131
- func prepareDynamicPreview (ctx context.Context , rw http.ResponseWriter , db database.Store , fc * files.Cache , templateVersion database.TemplateVersion , user database.User ) (render previewFunction , closer func (), success bool ) {
159
+ func prepareDynamicPreview (ctx context.Context , rw http.ResponseWriter , db database.Store , fc * files.Cache , tf database. TemplateVersionTerraformValue , templateVersion database.TemplateVersion , user database.User ) (render previewFunction , closer func (), success bool ) {
132
160
openFiles := make ([]uuid.UUID , 0 )
133
161
closeFiles := func () {
134
162
for _ , it := range openFiles {
@@ -167,36 +195,20 @@ func prepareDynamicPreview(ctx context.Context, rw http.ResponseWriter, db datab
167
195
// for populating values from data blocks, but isn't strictly required. If
168
196
// we don't have a cached plan available, we just use an empty one instead.
169
197
plan := json .RawMessage ("{}" )
170
- tf , err := db .GetTemplateVersionTerraformValues (ctx , templateVersion .ID )
171
- if err == nil {
172
- plan = tf .CachedPlan
173
-
174
- if tf .CachedModuleFiles .Valid {
175
- moduleFilesFS , err := fc .Acquire (fileCtx , tf .CachedModuleFiles .UUID )
176
- if err != nil {
177
- httpapi .Write (ctx , rw , http .StatusNotFound , codersdk.Response {
178
- Message : "Internal error fetching Terraform modules." ,
179
- Detail : err .Error (),
180
- })
181
- return nil , nil , false
182
- }
183
- openFiles = append (openFiles , tf .CachedModuleFiles .UUID )
198
+ plan = tf .CachedPlan
184
199
185
- templateFS , err = files . NewOverlayFS ( templateFS , []files. Overlay {{ Path : ".terraform/modules" , FS : moduleFilesFS }})
186
- if err != nil {
187
- httpapi . Write ( ctx , rw , http . StatusInternalServerError , codersdk. Response {
188
- Message : "Internal error creating overlay filesystem." ,
189
- Detail : err . Error () ,
190
- })
191
- return nil , nil , false
192
- }
200
+ if tf . CachedModuleFiles . Valid {
201
+ moduleFilesFS , err := fc . Acquire ( fileCtx , tf . CachedModuleFiles . UUID )
202
+ if err != nil {
203
+ httpapi . Write ( ctx , rw , http . StatusNotFound , codersdk. Response {
204
+ Message : "Internal error fetching Terraform modules." ,
205
+ Detail : err . Error (),
206
+ })
207
+ return nil , nil , false
193
208
}
194
- } else if ! xerrors .Is (err , sql .ErrNoRows ) {
195
- httpapi .Write (ctx , rw , http .StatusInternalServerError , codersdk.Response {
196
- Message : "Failed to retrieve Terraform values for template version" ,
197
- Detail : err .Error (),
198
- })
199
- return nil , nil , false
209
+ openFiles = append (openFiles , tf .CachedModuleFiles .UUID )
210
+
211
+ templateFS = files .NewOverlayFS (templateFS , []files.Overlay {{Path : ".terraform/modules" , FS : moduleFilesFS }})
200
212
}
201
213
202
214
owner , err := getWorkspaceOwnerData (ctx , db , user , templateVersion .OrganizationID )
@@ -219,15 +231,15 @@ func prepareDynamicPreview(ctx context.Context, rw http.ResponseWriter, db datab
219
231
}, closeFiles , true
220
232
}
221
233
222
- func staticPreview (ctx context.Context , db database.Store , version uuid.UUID ) func ( ctx context. Context , input preview. Input , fs fs. FS ) preview. Output {
234
+ func prepareStaticPreview (ctx context.Context , db database.Store , version uuid.UUID ) ( previewFunction , error ) {
223
235
dbTemplateVersionParameters , err := db .GetTemplateVersionParameters (ctx , version )
224
236
if err != nil {
225
- return nil
237
+ return nil , xerrors . Errorf ( "error fetching template version parameters: %w" , err )
226
238
}
227
239
228
240
params := make ([]previewtypes.Parameter , 0 , len (dbTemplateVersionParameters ))
229
241
for _ , it := range dbTemplateVersionParameters {
230
- params = append ( params , previewtypes.Parameter {
242
+ param := previewtypes.Parameter {
231
243
ParameterData : previewtypes.ParameterData {
232
244
Name : it .Name ,
233
245
DisplayName : it .DisplayName ,
@@ -240,22 +252,77 @@ func staticPreview(ctx context.Context, db database.Store, version uuid.UUID) fu
240
252
Icon : it .Icon ,
241
253
Options : nil ,
242
254
Validations : nil ,
243
- Required : false ,
244
- Order : 0 ,
245
- Ephemeral : false ,
255
+ Required : it . Required ,
256
+ Order : int64 ( it . DisplayOrder ) ,
257
+ Ephemeral : it . Ephemeral ,
246
258
Source : nil ,
247
259
},
248
- Value : previewtypes .NullString (),
260
+ // Always use the default, since we used to assume the empty string
261
+ Value : previewtypes .StringLiteral (it .DefaultValue ),
249
262
Diagnostics : nil ,
250
- })
251
- }
263
+ }
252
264
253
- return func (_ context.Context , in preview.Input , _ fs.FS ) preview.Output {
265
+ if it .ValidationError != "" || it .ValidationRegex != "" || it .ValidationMonotonic != "" {
266
+ var reg * string
267
+ if it .ValidationRegex != "" {
268
+ reg = ptr .Ref (it .ValidationRegex )
269
+ }
270
+
271
+ var vMin * int64
272
+ if it .ValidationMin .Valid {
273
+ vMin = ptr .Ref (int64 (it .ValidationMin .Int32 ))
274
+ }
254
275
255
- return preview.Output {
256
- Parameters : nil ,
276
+ var vMax * int64
277
+ if it .ValidationMax .Valid {
278
+ vMin = ptr .Ref (int64 (it .ValidationMax .Int32 ))
279
+ }
280
+
281
+ var monotonic * string
282
+ if it .ValidationMonotonic != "" {
283
+ monotonic = ptr .Ref (it .ValidationMonotonic )
284
+ }
285
+
286
+ param .Validations = append (param .Validations , & previewtypes.ParameterValidation {
287
+ Error : it .ValidationError ,
288
+ Regex : reg ,
289
+ Min : vMin ,
290
+ Max : vMax ,
291
+ Monotonic : monotonic ,
292
+ })
293
+ }
294
+
295
+ var protoOptions []* sdkproto.RichParameterOption
296
+ _ = json .Unmarshal (it .Options , & protoOptions ) // Not going to make this fatal
297
+ for _ , opt := range protoOptions {
298
+ param .Options = append (param .Options , & previewtypes.ParameterOption {
299
+ Name : opt .Name ,
300
+ Description : opt .Description ,
301
+ Value : previewtypes .StringLiteral (opt .Value ),
302
+ Icon : opt .Icon ,
303
+ })
257
304
}
305
+
306
+ param .Diagnostics = previewtypes .Diagnostics (param .Valid (param .Value ))
307
+ params = append (params , param )
258
308
}
309
+
310
+ return func (ctx context.Context , values map [string ]string ) (* preview.Output , hcl.Diagnostics ) {
311
+ for i := range params {
312
+ param := & params [i ]
313
+ paramValue , ok := values [param .Name ]
314
+ if ok {
315
+ param .Value = previewtypes .StringLiteral (paramValue )
316
+ } else {
317
+ paramValue = param .DefaultValue .AsString ()
318
+ }
319
+ param .Diagnostics = previewtypes .Diagnostics (param .Valid (param .Value ))
320
+ }
321
+
322
+ return & preview.Output {
323
+ Parameters : params ,
324
+ }, nil
325
+ }, nil
259
326
}
260
327
261
328
func getWorkspaceOwnerData (
0 commit comments