-
Notifications
You must be signed in to change notification settings - Fork 178
Create sandbox event endpoint and handlers #784
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d8dc71f
d7363e9
587f92e
61c8fe9
67abc7c
b39dd56
4aefc4a
78bfc5a
599531e
6f1aa77
c47a330
087c421
19a18a7
d8ca2e6
5aa2b58
59ea4ad
be5c2ad
eb5ca83
3179789
30e2cd8
881fbb0
89eb6fb
fdc5cdb
333bb17
e2e6e2c
6abcc04
fd4e9a1
b4bb3f6
7535b6e
d25a9f3
60de6a2
4309142
78ffeba
0b69432
6d666e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package internal | ||
|
||
import ( | ||
"github.com/e2b-dev/infra/packages/shared/pkg/env" | ||
) | ||
|
||
const ( | ||
defaultSandboxEventIP = "203.0.113.0" | ||
) | ||
|
||
func GetSandboxEventIP() string { | ||
return env.GetEnv("SANDBOX_EVENT_IP", defaultSandboxEventIP) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
package event | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"io" | ||
"net/http" | ||
"strings" | ||
|
||
"go.uber.org/zap" | ||
) | ||
|
||
type EventHandler interface { | ||
Path() string | ||
HandlerFunc(w http.ResponseWriter, r *http.Request) | ||
} | ||
|
||
type MetricsHandler struct{} | ||
|
||
func (h *MetricsHandler) Path() string { | ||
return "/metrics" | ||
} | ||
|
||
func (h *MetricsHandler) HandlerFunc(w http.ResponseWriter, r *http.Request) { | ||
if r.Method != http.MethodPost { | ||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) | ||
return | ||
} | ||
w.Header().Set("Content-Type", "application/json") | ||
w.WriteHeader(http.StatusCreated) | ||
_, err := w.Write([]byte(`{"event_ack":true,"path":"/metrics"}`)) | ||
if err != nil { | ||
http.Error(w, "Failed to write response", http.StatusInternalServerError) | ||
return | ||
} | ||
} | ||
|
||
// This is used to track ad-hoc events that are not handled by the event server. | ||
type DefaultHandler struct { | ||
store SandboxEventStore | ||
} | ||
|
||
func (h *DefaultHandler) Path() string { | ||
return "/" | ||
} | ||
|
||
func (h *DefaultHandler) HandlerFunc(w http.ResponseWriter, r *http.Request) { | ||
addr := r.RemoteAddr | ||
ip := strings.Split(addr, ":")[0] | ||
sandboxID, err := h.store.GetSandboxIP(ip) | ||
if err != nil { | ||
zap.L().Error("Failed to get sandbox ID from IP", zap.Error(err)) | ||
http.Error(w, "Error handling event", http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
zap.L().Debug("Received request from sandbox", zap.String("sandbox_id", sandboxID), zap.String("ip", ip)) | ||
|
||
if r.Method == http.MethodGet { | ||
events, err := h.store.GetLastNEvents(sandboxID, 10) | ||
if err != nil { | ||
zap.L().Error("Failed to get event data for sandbox "+sandboxID, zap.Error(err)) | ||
http.Error(w, "Failed to get event data for sandbox "+sandboxID, http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
eventJSON, err := json.Marshal(events) | ||
if err != nil { | ||
zap.L().Error("Failed to marshal event data", zap.Error(err)) | ||
http.Error(w, "Failed to marshal event data", http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
w.Header().Set("Content-Type", "application/json") | ||
w.WriteHeader(http.StatusOK) | ||
w.Write(eventJSON) | ||
return | ||
} | ||
|
||
if r.Method != http.MethodPost { | ||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) | ||
return | ||
} | ||
|
||
// Create event data with path and body | ||
eventData := SandboxEvent{ | ||
Path: r.URL.Path, | ||
} | ||
|
||
body, err := io.ReadAll(r.Body) | ||
if err != nil { | ||
http.Error(w, "Failed to read request body", http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
zap.L().Info("Received event", zap.String("body", string(body))) | ||
|
||
eventData.Body = make(map[string]any) | ||
err = json.Unmarshal(body, &eventData.Body) | ||
if err != nil { | ||
zap.L().Error("Failed to unmarshal request body", zap.Error(err)) | ||
http.Error(w, "Failed to unmarshal request body", http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
// Store in Redis with sandboxID as key | ||
err = h.store.AddEvent(sandboxID, &eventData, 0) | ||
if err != nil { | ||
zap.L().Error("Failed to store event data", zap.Error(err)) | ||
http.Error(w, "Failed to store event data", http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
w.WriteHeader(http.StatusCreated) | ||
w.Write([]byte(`{"event_ack":true}`)) | ||
} | ||
|
||
func NewEventHandlers(ctx context.Context, store SandboxEventStore) []EventHandler { | ||
return []EventHandler{ | ||
&MetricsHandler{}, | ||
&DefaultHandler{store}, | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package event | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
) | ||
|
||
// SandboxEventServer handles outbound HTTP requests from sandboxes calling the event.e2b.com endpoint | ||
type SandboxEventServer struct { | ||
server *http.Server | ||
} | ||
|
||
func NewSandboxEventServer(port uint, handlers []EventHandler) *SandboxEventServer { | ||
mux := http.NewServeMux() | ||
|
||
for _, handler := range handlers { | ||
mux.HandleFunc(handler.Path(), handler.HandlerFunc) | ||
} | ||
|
||
server := &http.Server{ | ||
Addr: fmt.Sprintf(":%d", port), | ||
Handler: mux, | ||
} | ||
Comment on lines
+21
to
+24
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about running Gin here so we can use a strict Golang client in envd and track all version changes? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sure, we can use gin, but i'm not sure it's more strict than the std lib and also what do u mean by tracking version changes in this case? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I was thinking that the "metrics server" can be Gin-backed by the OpenAPI scheme, as we are using for the API, so all breaking changes will be clear (because of the re-generated schema). You will also be able to use the generated Go client from EnvD to call metrics service endpoints. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The point was just make it strong types on both caller and receiver There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree with @sitole |
||
|
||
return &SandboxEventServer{ | ||
server: server, | ||
} | ||
} | ||
|
||
func (p *SandboxEventServer) Start() error { | ||
return p.server.ListenAndServe() | ||
} | ||
|
||
func (p *SandboxEventServer) Close(ctx context.Context) error { | ||
var err error | ||
select { | ||
case <-ctx.Done(): | ||
err = p.server.Close() | ||
default: | ||
err = p.server.Shutdown(ctx) | ||
} | ||
if err != nil { | ||
return fmt.Errorf("failed to shutdown event server: %w", err) | ||
} | ||
|
||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do you use TEST-NET-3 here?