Skip to content

Commit 7011e4b

Browse files
stirbyspikecurtismatifalimtojekethanndickson
authored
chore: cherry-pick patches for 2.17.3 (#15852)
Co-authored-by: Spike Curtis <spike@coder.com> Co-authored-by: Muhammad Atif Ali <atif@coder.com> Co-authored-by: Marcin Tojek <mtojek@users.noreply.github.com> Co-authored-by: Ethan <39577870+ethanndickson@users.noreply.github.com>
1 parent dbfadf2 commit 7011e4b

File tree

15 files changed

+186
-84
lines changed

15 files changed

+186
-84
lines changed

coderd/files.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ import (
2525
)
2626

2727
const (
28-
tarMimeType = "application/x-tar"
29-
zipMimeType = "application/zip"
28+
tarMimeType = "application/x-tar"
29+
zipMimeType = "application/zip"
30+
windowsZipMimeType = "application/x-zip-compressed"
3031

3132
HTTPFileMaxBytes = 10 * (10 << 20)
3233
)
@@ -48,7 +49,7 @@ func (api *API) postFile(rw http.ResponseWriter, r *http.Request) {
4849

4950
contentType := r.Header.Get("Content-Type")
5051
switch contentType {
51-
case tarMimeType, zipMimeType:
52+
case tarMimeType, zipMimeType, windowsZipMimeType:
5253
default:
5354
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
5455
Message: fmt.Sprintf("Unsupported content type header %q.", contentType),
@@ -66,7 +67,7 @@ func (api *API) postFile(rw http.ResponseWriter, r *http.Request) {
6667
return
6768
}
6869

69-
if contentType == zipMimeType {
70+
if contentType == zipMimeType || contentType == windowsZipMimeType {
7071
zipReader, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
7172
if err != nil {
7273
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{

coderd/files_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,18 @@ func TestPostFiles(t *testing.T) {
4343
require.NoError(t, err)
4444
})
4545

46+
t.Run("InsertWindowsZip", func(t *testing.T) {
47+
t.Parallel()
48+
client := coderdtest.New(t, nil)
49+
_ = coderdtest.CreateFirstUser(t, client)
50+
51+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
52+
defer cancel()
53+
54+
_, err := client.Upload(ctx, "application/x-zip-compressed", bytes.NewReader(archivetest.TestZipFileBytes()))
55+
require.NoError(t, err)
56+
})
57+
4658
t.Run("InsertAlreadyExists", func(t *testing.T) {
4759
t.Parallel()
4860
client := coderdtest.New(t, nil)

coderd/provisionerjobs.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"nhooyr.io/websocket"
1616

1717
"cdr.dev/slog"
18+
"github.com/coder/coder/v2/codersdk/wsjson"
1819

1920
"github.com/coder/coder/v2/coderd/database"
2021
"github.com/coder/coder/v2/coderd/database/db2sdk"
@@ -312,6 +313,7 @@ type logFollower struct {
312313
r *http.Request
313314
rw http.ResponseWriter
314315
conn *websocket.Conn
316+
enc *wsjson.Encoder[codersdk.ProvisionerJobLog]
315317

316318
jobID uuid.UUID
317319
after int64
@@ -391,6 +393,7 @@ func (f *logFollower) follow() {
391393
}
392394
defer f.conn.Close(websocket.StatusNormalClosure, "done")
393395
go httpapi.Heartbeat(f.ctx, f.conn)
396+
f.enc = wsjson.NewEncoder[codersdk.ProvisionerJobLog](f.conn, websocket.MessageText)
394397

395398
// query for logs once right away, so we can get historical data from before
396399
// subscription
@@ -488,11 +491,7 @@ func (f *logFollower) query() error {
488491
return xerrors.Errorf("error fetching logs: %w", err)
489492
}
490493
for _, log := range logs {
491-
logB, err := json.Marshal(convertProvisionerJobLog(log))
492-
if err != nil {
493-
return xerrors.Errorf("error marshaling log: %w", err)
494-
}
495-
err = f.conn.Write(f.ctx, websocket.MessageText, logB)
494+
err := f.enc.Encode(convertProvisionerJobLog(log))
496495
if err != nil {
497496
return xerrors.Errorf("error writing to websocket: %w", err)
498497
}

coderd/workspaceagents.go

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
"github.com/coder/coder/v2/codersdk"
3838
"github.com/coder/coder/v2/codersdk/agentsdk"
3939
"github.com/coder/coder/v2/codersdk/workspacesdk"
40+
"github.com/coder/coder/v2/codersdk/wsjson"
4041
"github.com/coder/coder/v2/tailnet"
4142
"github.com/coder/coder/v2/tailnet/proto"
4243
)
@@ -404,11 +405,9 @@ func (api *API) workspaceAgentLogs(rw http.ResponseWriter, r *http.Request) {
404405
}
405406
go httpapi.Heartbeat(ctx, conn)
406407

407-
ctx, wsNetConn := codersdk.WebsocketNetConn(ctx, conn, websocket.MessageText)
408-
defer wsNetConn.Close() // Also closes conn.
408+
encoder := wsjson.NewEncoder[[]codersdk.WorkspaceAgentLog](conn, websocket.MessageText)
409+
defer encoder.Close(websocket.StatusNormalClosure)
409410

410-
// The Go stdlib JSON encoder appends a newline character after message write.
411-
encoder := json.NewEncoder(wsNetConn)
412411
err = encoder.Encode(convertWorkspaceAgentLogs(logs))
413412
if err != nil {
414413
return
@@ -741,16 +740,8 @@ func (api *API) derpMapUpdates(rw http.ResponseWriter, r *http.Request) {
741740
})
742741
return
743742
}
744-
ctx, nconn := codersdk.WebsocketNetConn(ctx, ws, websocket.MessageBinary)
745-
defer nconn.Close()
746-
747-
// Slurp all packets from the connection into io.Discard so pongs get sent
748-
// by the websocket package. We don't do any reads ourselves so this is
749-
// necessary.
750-
go func() {
751-
_, _ = io.Copy(io.Discard, nconn)
752-
_ = nconn.Close()
753-
}()
743+
encoder := wsjson.NewEncoder[*tailcfg.DERPMap](ws, websocket.MessageBinary)
744+
defer encoder.Close(websocket.StatusGoingAway)
754745

755746
go func(ctx context.Context) {
756747
// TODO(mafredri): Is this too frequent? Use separate ping disconnect timeout?
@@ -768,7 +759,7 @@ func (api *API) derpMapUpdates(rw http.ResponseWriter, r *http.Request) {
768759
err := ws.Ping(ctx)
769760
cancel()
770761
if err != nil {
771-
_ = nconn.Close()
762+
_ = ws.Close(websocket.StatusGoingAway, "ping failed")
772763
return
773764
}
774765
}
@@ -781,9 +772,8 @@ func (api *API) derpMapUpdates(rw http.ResponseWriter, r *http.Request) {
781772
for {
782773
derpMap := api.DERPMap()
783774
if lastDERPMap == nil || !tailnet.CompareDERPMaps(lastDERPMap, derpMap) {
784-
err := json.NewEncoder(nconn).Encode(derpMap)
775+
err := encoder.Encode(derpMap)
785776
if err != nil {
786-
_ = nconn.Close()
787777
return
788778
}
789779
lastDERPMap = derpMap

codersdk/provisionerdaemons.go

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919

2020
"github.com/coder/coder/v2/buildinfo"
2121
"github.com/coder/coder/v2/codersdk/drpc"
22+
"github.com/coder/coder/v2/codersdk/wsjson"
2223
"github.com/coder/coder/v2/provisionerd/proto"
2324
"github.com/coder/coder/v2/provisionerd/runner"
2425
)
@@ -145,36 +146,8 @@ func (c *Client) provisionerJobLogsAfter(ctx context.Context, path string, after
145146
}
146147
return nil, nil, ReadBodyAsError(res)
147148
}
148-
logs := make(chan ProvisionerJobLog)
149-
closed := make(chan struct{})
150-
go func() {
151-
defer close(closed)
152-
defer close(logs)
153-
defer conn.Close(websocket.StatusGoingAway, "")
154-
var log ProvisionerJobLog
155-
for {
156-
msgType, msg, err := conn.Read(ctx)
157-
if err != nil {
158-
return
159-
}
160-
if msgType != websocket.MessageText {
161-
return
162-
}
163-
err = json.Unmarshal(msg, &log)
164-
if err != nil {
165-
return
166-
}
167-
select {
168-
case <-ctx.Done():
169-
return
170-
case logs <- log:
171-
}
172-
}
173-
}()
174-
return logs, closeFunc(func() error {
175-
<-closed
176-
return nil
177-
}), nil
149+
d := wsjson.NewDecoder[ProvisionerJobLog](conn, websocket.MessageText, c.logger)
150+
return d.Chan(), d, nil
178151
}
179152

180153
// ServeProvisionerDaemonRequest are the parameters to call ServeProvisionerDaemon with

codersdk/workspaceagents.go

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"nhooyr.io/websocket"
1616

1717
"github.com/coder/coder/v2/coderd/tracing"
18+
"github.com/coder/coder/v2/codersdk/wsjson"
1819
)
1920

2021
type WorkspaceAgentStatus string
@@ -454,30 +455,6 @@ func (c *Client) WorkspaceAgentLogsAfter(ctx context.Context, agentID uuid.UUID,
454455
}
455456
return nil, nil, ReadBodyAsError(res)
456457
}
457-
logChunks := make(chan []WorkspaceAgentLog, 1)
458-
closed := make(chan struct{})
459-
ctx, wsNetConn := WebsocketNetConn(ctx, conn, websocket.MessageText)
460-
decoder := json.NewDecoder(wsNetConn)
461-
go func() {
462-
defer close(closed)
463-
defer close(logChunks)
464-
defer conn.Close(websocket.StatusGoingAway, "")
465-
for {
466-
var logs []WorkspaceAgentLog
467-
err = decoder.Decode(&logs)
468-
if err != nil {
469-
return
470-
}
471-
select {
472-
case <-ctx.Done():
473-
return
474-
case logChunks <- logs:
475-
}
476-
}
477-
}()
478-
return logChunks, closeFunc(func() error {
479-
_ = wsNetConn.Close()
480-
<-closed
481-
return nil
482-
}), nil
458+
d := wsjson.NewDecoder[[]WorkspaceAgentLog](conn, websocket.MessageText, c.logger)
459+
return d.Chan(), d, nil
483460
}

codersdk/wsjson/decoder.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package wsjson
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"sync/atomic"
7+
8+
"nhooyr.io/websocket"
9+
10+
"cdr.dev/slog"
11+
)
12+
13+
type Decoder[T any] struct {
14+
conn *websocket.Conn
15+
typ websocket.MessageType
16+
ctx context.Context
17+
cancel context.CancelFunc
18+
chanCalled atomic.Bool
19+
logger slog.Logger
20+
}
21+
22+
// Chan starts the decoder reading from the websocket and returns a channel for reading the
23+
// resulting values. The chan T is closed if the underlying websocket is closed, or we encounter an
24+
// error. We also close the underlying websocket if we encounter an error reading or decoding.
25+
func (d *Decoder[T]) Chan() <-chan T {
26+
if !d.chanCalled.CompareAndSwap(false, true) {
27+
panic("chan called more than once")
28+
}
29+
values := make(chan T, 1)
30+
go func() {
31+
defer close(values)
32+
defer d.conn.Close(websocket.StatusGoingAway, "")
33+
for {
34+
// we don't use d.ctx here because it only gets canceled after closing the connection
35+
// and a "connection closed" type error is more clear than context canceled.
36+
typ, b, err := d.conn.Read(context.Background())
37+
if err != nil {
38+
// might be benign like EOF, so just log at debug
39+
d.logger.Debug(d.ctx, "error reading from websocket", slog.Error(err))
40+
return
41+
}
42+
if typ != d.typ {
43+
d.logger.Error(d.ctx, "websocket type mismatch while decoding")
44+
return
45+
}
46+
var value T
47+
err = json.Unmarshal(b, &value)
48+
if err != nil {
49+
d.logger.Error(d.ctx, "error unmarshalling", slog.Error(err))
50+
return
51+
}
52+
select {
53+
case values <- value:
54+
// OK
55+
case <-d.ctx.Done():
56+
return
57+
}
58+
}
59+
}()
60+
return values
61+
}
62+
63+
// nolint: revive // complains that Encoder has the same function name
64+
func (d *Decoder[T]) Close() error {
65+
err := d.conn.Close(websocket.StatusNormalClosure, "")
66+
d.cancel()
67+
return err
68+
}
69+
70+
// NewDecoder creates a JSON-over-websocket decoder for type T, which must be deserializable from
71+
// JSON.
72+
func NewDecoder[T any](conn *websocket.Conn, typ websocket.MessageType, logger slog.Logger) *Decoder[T] {
73+
ctx, cancel := context.WithCancel(context.Background())
74+
return &Decoder[T]{conn: conn, ctx: ctx, cancel: cancel, typ: typ, logger: logger}
75+
}

codersdk/wsjson/encoder.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package wsjson
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
7+
"golang.org/x/xerrors"
8+
"nhooyr.io/websocket"
9+
)
10+
11+
type Encoder[T any] struct {
12+
conn *websocket.Conn
13+
typ websocket.MessageType
14+
}
15+
16+
func (e *Encoder[T]) Encode(v T) error {
17+
w, err := e.conn.Writer(context.Background(), e.typ)
18+
if err != nil {
19+
return xerrors.Errorf("get websocket writer: %w", err)
20+
}
21+
defer w.Close()
22+
j := json.NewEncoder(w)
23+
err = j.Encode(v)
24+
if err != nil {
25+
return xerrors.Errorf("encode json: %w", err)
26+
}
27+
return nil
28+
}
29+
30+
func (e *Encoder[T]) Close(c websocket.StatusCode) error {
31+
return e.conn.Close(c, "")
32+
}
33+
34+
// NewEncoder creates a JSON-over websocket encoder for the type T, which must be JSON-serializable.
35+
// You may then call Encode() to send objects over the websocket. Creating an Encoder closes the
36+
// websocket for reading, turning it into a unidirectional write stream of JSON-encoded objects.
37+
func NewEncoder[T any](conn *websocket.Conn, typ websocket.MessageType) *Encoder[T] {
38+
// Here we close the websocket for reading, so that the websocket library will handle pings and
39+
// close frames.
40+
_ = conn.CloseRead(context.Background())
41+
return &Encoder[T]{conn: conn, typ: typ}
42+
}
3.35 MB
Loading

docs/user-guides/workspace-access/remote-desktops.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,12 @@ requires just a few lines of Terraform in your template, see the documentation
5858
on our registry for setup.
5959

6060
![Web RDP Module in a Workspace](../../images/user-guides/web-rdp-demo.png)
61+
62+
## Amazon DCV Windows
63+
64+
Our [Amazon DCV Windows](https://registry.coder.com/modules/amazon-dcv-windows)
65+
module adds a one-click button to open an Amazon DCV session in the browser.
66+
This requires just a few lines of Terraform in your template, see the
67+
documentation on our registry for setup.
68+
69+
![Amazon DCV Windows Module in a Workspace](../../images/user-guides/amazon-dcv-windows-demo.png)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy