Skip to content

Commit 8c23551

Browse files
committed
chore: add endpoint to designate user context
1 parent 9dca934 commit 8c23551

File tree

7 files changed

+99
-21
lines changed

7 files changed

+99
-21
lines changed

cli/web.go

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717

1818
"cdr.dev/slog"
1919
"cdr.dev/slog/sloggers/sloghuman"
20+
"github.com/coder/preview/types"
2021
"github.com/coder/preview/web"
2122
"github.com/coder/serpent"
2223
"github.com/coder/websocket"
@@ -80,6 +81,15 @@ func (r *RootCmd) WebsocketServer() *serpent.Command {
8081

8182
mux.Use(debugMiddleware(logger))
8283

84+
mux.HandleFunc("/users/{dir}", func(rw http.ResponseWriter, r *http.Request) {
85+
dirFS := os.DirFS(chi.URLParam(r, "dir"))
86+
availableUsers, err := availableUsers(dirFS)
87+
if err != nil {
88+
http.Error(rw, err.Error(), http.StatusInternalServerError)
89+
return
90+
}
91+
_ = json.NewEncoder(rw).Encode(availableUsers)
92+
})
8393
mux.HandleFunc("/directories", func(rw http.ResponseWriter, r *http.Request) {
8494
entries, err := os.ReadDir(".")
8595
if err != nil {
@@ -132,19 +142,19 @@ func (r *RootCmd) WebsocketServer() *serpent.Command {
132142

133143
func websocketHandler(logger slog.Logger) func(rw http.ResponseWriter, r *http.Request) {
134144
return func(rw http.ResponseWriter, r *http.Request) {
135-
136-
logger.Debug(r.Context(), "WebSocket connection attempt",
145+
146+
logger.Debug(r.Context(), "WebSocket connection attempt",
137147
slog.F("remote_addr", r.RemoteAddr),
138148
slog.F("path", r.URL.Path),
139149
slog.F("query", r.URL.RawQuery))
140-
150+
141151
// Validate all parameters BEFORE upgrading the connection
142152
dir := chi.URLParam(r, "dir")
143153
logger.Debug(r.Context(), "Directory parameter", slog.F("dir", dir))
144-
154+
145155
dinfo, err := os.Stat(dir)
146156
if err != nil {
147-
logger.Error(r.Context(), "Directory validation failed",
157+
logger.Error(r.Context(), "Directory validation failed",
148158
slog.Error(err),
149159
slog.F("dir", dir))
150160
http.Error(rw, "Could not stat directory: "+err.Error(), http.StatusBadRequest)
@@ -173,11 +183,57 @@ func websocketHandler(logger slog.Logger) func(rw http.ResponseWriter, r *http.R
173183
return
174184
}
175185
logger.Debug(r.Context(), "WebSocket connection established")
176-
186+
187+
var owner types.WorkspaceOwner
177188
dirFS := os.DirFS(dir)
178189
planPath := r.URL.Query().Get("plan")
190+
user := r.URL.Query().Get("user")
191+
if user != "" {
192+
available, err := availableUsers(dirFS)
193+
if err != nil {
194+
_ = conn.Close(websocket.StatusInternalError, err.Error())
195+
return
196+
}
197+
198+
var ok bool
199+
owner, ok = available[user]
200+
if !ok {
201+
_ = conn.Close(websocket.StatusInternalError, err.Error())
202+
return
203+
}
204+
}
179205

180-
session := web.NewSession(logger, dirFS, planPath)
206+
session := web.NewSession(logger, dirFS, web.SessionInputs{
207+
PlanPath: planPath,
208+
User: owner,
209+
})
181210
session.Listen(r.Context(), conn)
182211
}
183212
}
213+
214+
func availableUsers(dirFS fs.FS) (map[string]types.WorkspaceOwner, error) {
215+
entries, err := fs.ReadDir(dirFS, ".")
216+
if err != nil {
217+
return nil, fmt.Errorf("could not read directory: %w", err)
218+
}
219+
220+
idx := slices.IndexFunc(entries, func(entry fs.DirEntry) bool {
221+
return entry.Name() == "users.json"
222+
})
223+
if idx == -1 {
224+
return map[string]types.WorkspaceOwner{}, nil
225+
}
226+
227+
file, err := dirFS.Open(entries[idx].Name())
228+
if err != nil {
229+
return nil, fmt.Errorf("could not open users file: %w", err)
230+
}
231+
defer file.Close()
232+
233+
var users map[string]types.WorkspaceOwner
234+
if err := json.NewDecoder(file).Decode(&users); err != nil {
235+
return nil, fmt.Errorf("could not decode users file: %w", err)
236+
}
237+
238+
return users, nil
239+
}

testdata/demo/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Regions exists. Users are defaulted to their region based on their groups.
1010
- `eu-helsinki`
1111
- `ap-sydney`
1212
- `sa-saopaulo`
13-
- `za-jnb`.
13+
- `za-jnb`
1414

1515
Select your IDE from jetbrains based on your team.
1616
- Frontend

testdata/demo/parameters.tf

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ data "coder_parameter" "cpu" {
2525
name = "cpu"
2626
display_name = "CPU"
2727
description = "The number of CPU cores"
28-
type ="number"
28+
type = "number"
2929
default = "2"
3030
icon = "/icon/memory.svg"
3131
mutable = true
@@ -34,7 +34,8 @@ data "coder_parameter" "cpu" {
3434
min = 1
3535
// Confidential instances are more expensive, or some justification like
3636
// that
37-
max = module.base.security_level == "high" ? 4: 8
37+
// TODO: This breaks when the user is an admin
38+
max = module.base.security_level == "high" ? 4 : 8
3839
error = "CPU range must be between {min} and {max}."
3940
}
4041
}

testdata/demo/plan.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

testdata/demo/users.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"administrator": {
3+
"groups": ["admin"]
4+
},
5+
"developer": {
6+
"groups": ["developer"]
7+
},
8+
"contractor": {
9+
"groups": ["contractor"]
10+
},
11+
"eu-developer": {
12+
"groups": ["developer", "eu-helsinki"]
13+
}
14+
}

types/owner.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package types
22

33
type WorkspaceOwner struct {
4-
Groups []string
4+
Groups []string `json:"groups"`
55
}

web/session.go

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,26 @@ type Response struct {
2525

2626
// @typescript-ignore Session
2727
type Session struct {
28-
logger slog.Logger
29-
dir fs.FS
30-
planPath string
28+
logger slog.Logger
29+
dir fs.FS
30+
staticInputs SessionInputs
3131

3232
requests chan *Request
3333
responses chan *Response
3434
}
3535

36-
func NewSession(logger slog.Logger, dir fs.FS, planPath string) *Session {
36+
type SessionInputs struct {
37+
PlanPath string
38+
User types.WorkspaceOwner
39+
}
40+
41+
func NewSession(logger slog.Logger, dir fs.FS, staticInputs SessionInputs) *Session {
3742
return &Session{
38-
logger: logger,
39-
dir: dir,
40-
planPath: planPath,
41-
requests: make(chan *Request, 2),
42-
responses: make(chan *Response, 2),
43+
logger: logger,
44+
dir: dir,
45+
staticInputs: staticInputs,
46+
requests: make(chan *Request, 2),
47+
responses: make(chan *Response, 2),
4348
}
4449
}
4550

@@ -67,8 +72,9 @@ func (s *Session) sendRequest(ctx context.Context, req Request) {
6772

6873
func (s *Session) preview(ctx context.Context, req *Request) Response {
6974
output, diags := preview.Preview(ctx, preview.Input{
70-
PlanJSONPath: s.planPath,
75+
PlanJSONPath: s.staticInputs.PlanPath,
7176
ParameterValues: req.Inputs,
77+
Owner: s.staticInputs.User,
7278
}, s.dir)
7379

7480
r := Response{

0 commit comments

Comments
 (0)