Skip to content

Commit 04a4e43

Browse files
committed
Initial spike with request/event handling
1 parent bf83c66 commit 04a4e43

File tree

5 files changed

+159
-4
lines changed

5 files changed

+159
-4
lines changed

pkg/cmd/codespace/ports.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import (
77
"errors"
88
"fmt"
99
"net"
10+
"net/http"
1011
"strconv"
1112
"strings"
13+
"time"
1214

1315
"github.com/cli/cli/v2/internal/codespaces"
1416
"github.com/cli/cli/v2/internal/codespaces/api"
@@ -253,6 +255,15 @@ func (a *App) UpdatePortVisibility(ctx context.Context, codespaceName string, ar
253255
for _, port := range ports {
254256
a.StartProgressIndicatorWithLabel(fmt.Sprintf("Updating port %d visibility to: %s", port.number, port.visibility))
255257
err := session.UpdateSharedServerPrivacy(ctx, port.number, port.visibility)
258+
259+
// wait for succeed or failure
260+
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
261+
defer cancel()
262+
263+
if err := a.waitForPortUpdate(ctx, session, port.number); err != nil {
264+
return fmt.Errorf("error waiting for port update: %w", err)
265+
}
266+
256267
a.StopProgressIndicator()
257268
if err != nil {
258269
return fmt.Errorf("error update port to public: %w", err)
@@ -262,6 +273,48 @@ func (a *App) UpdatePortVisibility(ctx context.Context, codespaceName string, ar
262273
return nil
263274
}
264275

276+
type portChangeKind string
277+
278+
const (
279+
portChangeKindUpdate portChangeKind = "update"
280+
)
281+
282+
type portData struct {
283+
Port int `json:"port"`
284+
ChangeKind portChangeKind `json:"changeKind"`
285+
ErrorDetail string `json:"errorDetail"`
286+
StatusCode int `json:"statusCode"`
287+
}
288+
289+
func (a *App) waitForPortUpdate(ctx context.Context, session *liveshare.Session, port int) error {
290+
success := session.WaitForEvent("sharingSucceeded")
291+
failure := session.WaitForEvent("sharingFailed")
292+
293+
for {
294+
select {
295+
case <-ctx.Done():
296+
return fmt.Errorf("timeout waiting for server sharing to succeed or fail")
297+
case b := <-success:
298+
if err := json.Unmarshal(b, &portData); err != nil {
299+
return fmt.Errorf("error unmarshaling port data: %w", err)
300+
}
301+
if portData.Port == port && portData.ChangeKind == portChangeKindUpdate {
302+
return nil
303+
}
304+
case b := <-failure:
305+
if err := json.Unmarshal(b, &portData); err != nil {
306+
return fmt.Errorf("error unmarshaling port data: %w", err)
307+
}
308+
if portData.Port == port && portData.ChangeKind == portChangeKindUpdate {
309+
if portData.StatusCode == http.StatusForbidden {
310+
return errors.New("organization admin has forbidden this privacy setting")
311+
}
312+
return errors.New(portData.ErrorDetail)
313+
}
314+
}
315+
}
316+
}
317+
265318
type portVisibility struct {
266319
number int
267320
visibility string

pkg/liveshare/port_forwarder.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ func (fwd *PortForwarder) shareRemotePort(ctx context.Context) (channelID, error
9797
if err != nil {
9898
err = fmt.Errorf("failed to share remote port %d: %w", fwd.remotePort, err)
9999
}
100+
101+
// wait for port change kind start
102+
100103
return id, err
101104
}
102105

pkg/liveshare/rpc.go

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"io"
7+
"sync"
78
"time"
89

910
"github.com/opentracing/opentracing-go"
@@ -13,15 +14,18 @@ import (
1314
type rpcClient struct {
1415
*jsonrpc2.Conn
1516
conn io.ReadWriteCloser
17+
18+
eventHandlersMu sync.RWMutex
19+
eventHandlers map[string]chan []byte
1620
}
1721

1822
func newRPCClient(conn io.ReadWriteCloser) *rpcClient {
19-
return &rpcClient{conn: conn}
23+
return &rpcClient{conn: conn, eventHandlers: make(map[string]chan []byte)}
2024
}
2125

2226
func (r *rpcClient) connect(ctx context.Context) {
2327
stream := jsonrpc2.NewBufferedStream(r.conn, jsonrpc2.VSCodeObjectCodec{})
24-
r.Conn = jsonrpc2.NewConn(ctx, stream, nullHandler{})
28+
r.Conn = jsonrpc2.NewConn(ctx, stream, newRequestHandler(r))
2529
}
2630

2731
func (r *rpcClient) do(ctx context.Context, method string, args, result interface{}) error {
@@ -40,7 +44,43 @@ func (r *rpcClient) do(ctx context.Context, method string, args, result interfac
4044
return waiter.Wait(waitCtx, result)
4145
}
4246

43-
type nullHandler struct{}
47+
func (r *rpcClient) registerEventHandler(eventName string) chan []byte {
48+
r.eventHandlersMu.Lock()
49+
defer r.eventHandlersMu.Unlock()
50+
51+
if ch, ok := r.eventHandlers[eventName]; ok {
52+
return ch
53+
}
54+
55+
ch := make(chan []byte)
56+
r.eventHandlers[eventName] = ch
57+
return ch
58+
}
59+
60+
func (r *rpcClient) eventHandler(eventName string) chan []byte {
61+
r.eventHandlersMu.RLock()
62+
defer r.eventHandlersMu.RUnlock()
63+
64+
return r.eventHandlers[eventName]
65+
}
4466

45-
func (nullHandler) Handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) {
67+
type requestHandler struct {
68+
rpcClient *rpcClient
69+
}
70+
71+
func newRequestHandler(rpcClient *rpcClient) *requestHandler {
72+
return &requestHandler{rpcClient: rpcClient}
73+
}
74+
75+
func (e *requestHandler) Handle(ctx context.Context, conn *jsonrpc2.Conn, req *jsonrpc2.Request) {
76+
handler := e.rpcClient.eventHandler(req.Method)
77+
if handler == nil {
78+
return // noop
79+
}
80+
81+
select {
82+
case handler <- *req.Params:
83+
default:
84+
// event handler
85+
}
4686
}

pkg/liveshare/rpc_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package liveshare
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"net"
8+
"testing"
9+
10+
"github.com/sourcegraph/jsonrpc2"
11+
)
12+
13+
func TestRequestHandler(t *testing.T) {
14+
r, w := net.Pipe()
15+
client := newRPCClient(r)
16+
17+
ctx := context.Background()
18+
client.connect(ctx)
19+
20+
type params struct {
21+
Data string `json:"data"`
22+
}
23+
24+
ev := client.registerEventHandler("testEvent")
25+
done := make(chan error)
26+
go func() {
27+
b := <-ev
28+
var receivedParams params
29+
if err := json.Unmarshal(b, &receivedParams); err != nil {
30+
done <- err
31+
return
32+
}
33+
if receivedParams.Data != "test" {
34+
done <- fmt.Errorf("expected test, got %q", receivedParams.Data)
35+
}
36+
done <- nil
37+
}()
38+
39+
go func() {
40+
codec := jsonrpc2.VSCodeObjectCodec{}
41+
type message struct {
42+
Method string `json:"method"`
43+
Params params `json:"params"`
44+
}
45+
codec.WriteObject(w, message{
46+
Method: "testEvent",
47+
Params: params{"test"},
48+
})
49+
}()
50+
51+
err := <-done
52+
if err != nil {
53+
t.Fatal(err)
54+
}
55+
}

pkg/liveshare/session.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ func (s *Session) UpdateSharedServerPrivacy(ctx context.Context, port int, visib
7878
return nil
7979
}
8080

81+
func (s *Session) WaitForEvent(eventName string) chan []byte {
82+
return s.rpc.registerEventHandler(eventName)
83+
}
84+
8185
// StartsSSHServer starts an SSH server in the container, installing sshd if necessary,
8286
// and returns the port on which it listens and the user name clients should provide.
8387
func (s *Session) StartSSHServer(ctx context.Context) (int, string, error) {

0 commit comments

Comments
 (0)