6
6
"encoding/json"
7
7
"errors"
8
8
"fmt"
9
+ "io"
9
10
"net/http"
10
11
"slices"
11
12
"strconv"
@@ -15,6 +16,7 @@ import (
15
16
"github.com/go-chi/chi/v5"
16
17
"github.com/google/uuid"
17
18
"golang.org/x/xerrors"
19
+ "nhooyr.io/websocket"
18
20
19
21
"cdr.dev/slog"
20
22
"github.com/coder/coder/v2/agent/proto"
@@ -36,6 +38,7 @@ import (
36
38
"github.com/coder/coder/v2/coderd/wsbuilder"
37
39
"github.com/coder/coder/v2/codersdk"
38
40
"github.com/coder/coder/v2/codersdk/agentsdk"
41
+ "github.com/coder/coder/v2/tailnet"
39
42
)
40
43
41
44
var (
@@ -2068,6 +2071,11 @@ func (api *API) publishWorkspaceUpdate(ctx context.Context, workspaceID uuid.UUI
2068
2071
api .Logger .Warn (ctx , "failed to publish workspace update" ,
2069
2072
slog .F ("workspace_id" , workspaceID ), slog .Error (err ))
2070
2073
}
2074
+ err = api .Pubsub .Publish (codersdk .AllWorkspacesNotifyChannel , []byte (workspaceID .String ()))
2075
+ if err != nil {
2076
+ api .Logger .Warn (ctx , "failed to publish all workspaces update" ,
2077
+ slog .F ("workspace_id" , workspaceID ), slog .Error (err ))
2078
+ }
2071
2079
}
2072
2080
2073
2081
func (api * API ) publishWorkspaceAgentLogsUpdate (ctx context.Context , workspaceAgentID uuid.UUID , m agentsdk.LogsNotifyMessage ) {
@@ -2080,3 +2088,72 @@ func (api *API) publishWorkspaceAgentLogsUpdate(ctx context.Context, workspaceAg
2080
2088
api .Logger .Warn (ctx , "failed to publish workspace agent logs update" , slog .F ("workspace_agent_id" , workspaceAgentID ), slog .Error (err ))
2081
2089
}
2082
2090
}
2091
+
2092
+ // @Summary Coordinate multiple workspace agents
2093
+ // @ID coordinate-multiple-workspace-agents
2094
+ // @Security CoderSessionToken
2095
+ // @Tags Workspaces
2096
+ // @Success 101
2097
+ // @Router /users/me/tailnet [get]
2098
+ func (api * API ) tailnet (rw http.ResponseWriter , r * http.Request ) {
2099
+ ctx := r .Context ()
2100
+ owner := httpmw .UserParam (r )
2101
+ ownerRoles := httpmw .UserAuthorization (r )
2102
+
2103
+ // Check if the actor is allowed to access any workspace owned by the user.
2104
+ if ! api .Authorize (r , policy .ActionSSH , rbac .ResourceWorkspace .WithOwner (owner .ID .String ())) {
2105
+ httpapi .ResourceNotFound (rw )
2106
+ return
2107
+ }
2108
+
2109
+ version := "1.0"
2110
+ qv := r .URL .Query ().Get ("version" )
2111
+ if qv != "" {
2112
+ version = qv
2113
+ }
2114
+ if err := proto .CurrentVersion .Validate (version ); err != nil {
2115
+ httpapi .Write (ctx , rw , http .StatusBadRequest , codersdk.Response {
2116
+ Message : "Unknown or unsupported API version" ,
2117
+ Validations : []codersdk.ValidationError {
2118
+ {Field : "version" , Detail : err .Error ()},
2119
+ },
2120
+ })
2121
+ return
2122
+ }
2123
+
2124
+ peerID , err := api .handleResumeToken (ctx , rw , r )
2125
+ if err != nil {
2126
+ // handleResumeToken has already written the response.
2127
+ return
2128
+ }
2129
+
2130
+ api .WebsocketWaitMutex .Lock ()
2131
+ api .WebsocketWaitGroup .Add (1 )
2132
+ api .WebsocketWaitMutex .Unlock ()
2133
+ defer api .WebsocketWaitGroup .Done ()
2134
+
2135
+ conn , err := websocket .Accept (rw , r , nil )
2136
+ if err != nil {
2137
+ httpapi .Write (ctx , rw , http .StatusBadRequest , codersdk.Response {
2138
+ Message : "Failed to accept websocket." ,
2139
+ Detail : err .Error (),
2140
+ })
2141
+ return
2142
+ }
2143
+ ctx , wsNetConn := codersdk .WebsocketNetConn (ctx , conn , websocket .MessageBinary )
2144
+ defer wsNetConn .Close ()
2145
+ defer conn .Close (websocket .StatusNormalClosure , "" )
2146
+
2147
+ go httpapi .Heartbeat (ctx , conn )
2148
+ err = api .TailnetClientService .ServeUserClient (ctx , version , wsNetConn , tailnet.ServeUserClientOptions {
2149
+ PeerID : peerID ,
2150
+ UserID : owner .ID ,
2151
+ Subject : & ownerRoles ,
2152
+ Authz : api .Authorizer ,
2153
+ Database : api .Database ,
2154
+ })
2155
+ if err != nil && ! xerrors .Is (err , io .EOF ) && ! xerrors .Is (err , context .Canceled ) {
2156
+ _ = conn .Close (websocket .StatusInternalError , err .Error ())
2157
+ return
2158
+ }
2159
+ }
0 commit comments