Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 18ff0b4

Browse files
committed
rebase
1 parent 74e8327 commit 18ff0b4

File tree

8 files changed

+1879
-108
lines changed

8 files changed

+1879
-108
lines changed

coderd/apidoc/docs.go

+32
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+28
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/coderd.go

+2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import (
4343

4444
"github.com/coder/coder/v2/coderd/cryptokeys"
4545
"github.com/coder/coder/v2/coderd/entitlements"
46+
"github.com/coder/coder/v2/coderd/files"
4647
"github.com/coder/coder/v2/coderd/idpsync"
4748
"github.com/coder/coder/v2/coderd/runtimeconfig"
4849
"github.com/coder/coder/v2/coderd/webpush"
@@ -1527,6 +1528,7 @@ type API struct {
15271528
// passed to dbauthz.
15281529
AccessControlStore *atomic.Pointer[dbauthz.AccessControlStore]
15291530
PortSharer atomic.Pointer[portsharing.PortSharer]
1531+
FileCache files.Cache
15301532

15311533
UpdatesProvider tailnet.WorkspaceUpdatesProvider
15321534

coderd/templateversions.go

+110
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"encoding/json"
99
"errors"
1010
"fmt"
11+
"math"
1112
"net/http"
1213
"os"
1314

@@ -35,10 +36,15 @@ import (
3536
"github.com/coder/coder/v2/coderd/tracing"
3637
"github.com/coder/coder/v2/coderd/util/ptr"
3738
"github.com/coder/coder/v2/codersdk"
39+
"github.com/coder/coder/v2/codersdk/wsjson"
3840
"github.com/coder/coder/v2/examples"
3941
"github.com/coder/coder/v2/provisioner/terraform/tfparse"
4042
"github.com/coder/coder/v2/provisionersdk"
4143
sdkproto "github.com/coder/coder/v2/provisionersdk/proto"
44+
"github.com/coder/preview"
45+
previewtypes "github.com/coder/preview/types"
46+
previewweb "github.com/coder/preview/web"
47+
"github.com/coder/websocket"
4248
)
4349

4450
// @Summary Get template version by ID
@@ -266,6 +272,110 @@ func (api *API) patchCancelTemplateVersion(rw http.ResponseWriter, r *http.Reque
266272
})
267273
}
268274

275+
// @Summary Open dynamic parameters WebSocket by template version
276+
// @ID open-dynamic-parameters-websocket-by-template-version
277+
// @Security CoderSessionToken
278+
// @Produce json
279+
// @Tags Templates
280+
// @Param templateversion path string true "Template version ID" format(uuid)
281+
// @Success 101
282+
// @Router /templateversions/{templateversion}/dynamic-parameters [get]
283+
func (api *API) templateVersionDynamicParameters(rw http.ResponseWriter, r *http.Request) {
284+
ctx := r.Context()
285+
templateVersion := httpmw.TemplateVersionParam(r)
286+
287+
// Check that the job has completed successfully
288+
job, err := api.Database.GetProvisionerJobByID(ctx, templateVersion.JobID)
289+
if err != nil {
290+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
291+
Message: "Internal error fetching provisioner job.",
292+
Detail: err.Error(),
293+
})
294+
return
295+
}
296+
if !job.CompletedAt.Valid {
297+
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
298+
Message: "Job hasn't completed!",
299+
})
300+
return
301+
}
302+
303+
// Having the Terraform plan available for the evaluation engine is helpful
304+
// for populating values from data blocks, but isn't strictly required. If
305+
// we don't have a cached plan available, we just use an empty one instead.
306+
var plan json.RawMessage = []byte("{}")
307+
tf, err := api.Database.GetTemplateVersionTerraformValues(ctx, templateVersion.ID)
308+
if err == nil {
309+
plan = tf.CachedPlan
310+
}
311+
312+
input := preview.Input{
313+
PlanJSON: plan,
314+
ParameterValues: map[string]string{},
315+
Owner: previewtypes.WorkspaceOwner{},
316+
}
317+
318+
fileCtx := dbauthz.AsProvisionerd(ctx)
319+
fileID, err := api.Database.GetFileIDByTemplateVersionID(fileCtx, templateVersion.ID)
320+
if err != nil {
321+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
322+
Message: "Internal error finding template version Terraform.",
323+
Detail: err.Error(),
324+
})
325+
return
326+
}
327+
328+
fs, err := api.FileCache.Acquire(fileCtx, fileID)
329+
defer api.FileCache.Release(fileID)
330+
if err != nil {
331+
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
332+
Message: "Internal error fetching template version Terraform.",
333+
Detail: err.Error(),
334+
})
335+
return
336+
}
337+
338+
conn, err := websocket.Accept(rw, r, nil)
339+
if err != nil {
340+
httpapi.Write(ctx, rw, http.StatusUpgradeRequired, codersdk.Response{
341+
Message: "Failed to accept WebSocket.",
342+
Detail: err.Error(),
343+
})
344+
return
345+
}
346+
347+
stream := wsjson.NewStream[previewweb.Request, previewweb.Response](conn, websocket.MessageText, websocket.MessageText, api.Logger)
348+
349+
// Send an initial form state, computed without any user input.
350+
result, diagnostics := preview.Preview(ctx, input, fs)
351+
stream.Send(previewweb.Response{
352+
// or maybe it could be -1 or something? it just has to be unique from
353+
// anything a client could reasonably send.
354+
ID: math.MaxInt32,
355+
Parameters: result.Parameters,
356+
Diagnostics: previewtypes.Diagnostics(diagnostics),
357+
})
358+
359+
// As the user types into the form, reprocess the state using their input,
360+
// and respond with updates.
361+
updates := stream.Chan()
362+
for {
363+
select {
364+
case <-ctx.Done():
365+
return
366+
case update := <-updates:
367+
newInput := input
368+
newInput.ParameterValues = update.Inputs
369+
result, diagnostics := preview.Preview(ctx, input, fs)
370+
stream.Send(previewweb.Response{
371+
ID: update.ID,
372+
Parameters: result.Parameters,
373+
Diagnostics: previewtypes.Diagnostics(diagnostics),
374+
})
375+
}
376+
}
377+
}
378+
269379
// @Summary Get rich parameters by template version
270380
// @ID get-rich-parameters-by-template-version
271381
// @Security CoderSessionToken

codersdk/wsjson/stream.go

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package wsjson
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
7+
"cdr.dev/slog"
8+
"github.com/coder/websocket"
9+
"golang.org/x/xerrors"
10+
)
11+
12+
// Stream is a two-way messaging interface over a WebSocket connection.
13+
// As an implementation detail, we cannot currently use Encoder to implement
14+
// the writing side of things because it only supports sending one message, and
15+
// then immediately closing the WebSocket.
16+
type Stream[R any, W any] struct {
17+
conn *websocket.Conn
18+
r *Decoder[R]
19+
20+
writeType websocket.MessageType
21+
}
22+
23+
func NewStream[R any, W any](conn *websocket.Conn, readType, writeType websocket.MessageType, logger slog.Logger) *Stream[R, W] {
24+
return &Stream[R, W]{
25+
conn: conn,
26+
r: NewDecoder[R](conn, readType, logger),
27+
writeType: writeType,
28+
}
29+
}
30+
31+
func (s *Stream[R, W]) Chan() <-chan R {
32+
return s.r.Chan()
33+
}
34+
35+
func (s *Stream[R, W]) Send(v W) error {
36+
w, err := s.conn.Writer(context.Background(), s.writeType)
37+
if err != nil {
38+
return xerrors.Errorf("get websocket writer: %w", err)
39+
}
40+
j := json.NewEncoder(w)
41+
err = j.Encode(v)
42+
if err != nil {
43+
return xerrors.Errorf("encode json: %w", err)
44+
}
45+
return nil
46+
}
47+
48+
func (s *Stream[R, W]) Close(c websocket.StatusCode) error {
49+
return s.conn.Close(c, "")
50+
}

docs/reference/api/templates.md

+26
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)