Skip to content

Commit 6c06493

Browse files
committed
fix: reconcile sandbox preview reference grants
1 parent 05ec052 commit 6c06493

8 files changed

Lines changed: 807 additions & 11 deletions

File tree

Tiltfile.cluster

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -931,8 +931,20 @@ local_resource(
931931
# KRO_KUBECONFIG points at the kind cluster's host kubeconfig (where kro is).
932932
serve_cmd=('KRO_KUBECONFIG={kro} INFRASTRUCTURE_KUBECONFIG={kc} ' +
933933
'INFRASTRUCTURE_WORKSPACE_PATH=root:kedge:providers:infrastructure ' +
934+
'KEDGE_SANDBOX_PREVIEW_BASE_DOMAIN={preview_domain} ' +
935+
'KEDGE_SANDBOX_PREVIEW_HTTPROUTE_PARENT_GATEWAY_NAME={preview_gateway} ' +
936+
'KEDGE_SANDBOX_PREVIEW_HTTPROUTE_PARENT_GATEWAY_NAMESPACE=envoy-gateway-system ' +
937+
'KEDGE_SANDBOX_PREVIEW_HTTPROUTE_PARENT_GATEWAY_SECTION_NAME={preview_listener} ' +
938+
'KEDGE_SANDBOX_PREVIEW_BACKEND_NAMESPACE={preview_namespace} ' +
939+
'KEDGE_SANDBOX_PREVIEW_BACKEND_SERVICE_NAME={preview_backend} ' +
940+
'KEDGE_SANDBOX_PREVIEW_BACKEND_SERVICE_PORT=8080 ' +
934941
'make run-provider-infrastructure').format(
935-
kro=KIND_HOST_KUBECONFIG, kc=KCP_ADMIN_KUBECONFIG),
942+
kro=KIND_HOST_KUBECONFIG, kc=KCP_ADMIN_KUBECONFIG,
943+
preview_domain=app_studio_preview_base_domain,
944+
preview_gateway=app_studio_preview_parent_gateway_name,
945+
preview_listener=app_studio_preview_listener,
946+
preview_namespace=app_studio_preview_namespace,
947+
preview_backend=app_studio_preview_gateway_name),
936948
deps=[
937949
'providers/infrastructure/main.go',
938950
'providers/infrastructure/heartbeat.go',

providers/infrastructure/dataplane/handler.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,31 @@ type InstanceGetter interface {
5252
// target via the template contract, and reverse-proxies to the runtime cluster
5353
// the provider owns. Consumers therefore never hold a runtime credential.
5454
type Handler struct {
55-
instances InstanceGetter
56-
contracts ContractGetter
57-
runtime Runtime
55+
instances InstanceGetter
56+
contracts ContractGetter
57+
runtime Runtime
58+
previewRoutes PreviewRouteEnsurer
59+
}
60+
61+
// HandlerOption customizes the data-plane handler.
62+
type HandlerOption func(*Handler)
63+
64+
// WithPreviewRouteManager installs the infrastructure-owned preview route
65+
// reconciler. It is only used for SandboxRunner proxy requests.
66+
func WithPreviewRouteManager(manager PreviewRouteEnsurer) HandlerOption {
67+
return func(h *Handler) {
68+
h.previewRoutes = manager
69+
}
5870
}
5971

6072
// NewHandler wires the handler. Any nil dependency makes the data plane report
6173
// 503 (the serve process runs without a runtime/kcp config in dev).
62-
func NewHandler(instances InstanceGetter, contracts ContractGetter, runtime Runtime) *Handler {
63-
return &Handler{instances: instances, contracts: contracts, runtime: runtime}
74+
func NewHandler(instances InstanceGetter, contracts ContractGetter, runtime Runtime, opts ...HandlerOption) *Handler {
75+
h := &Handler{instances: instances, contracts: contracts, runtime: runtime}
76+
for _, opt := range opts {
77+
opt(h)
78+
}
79+
return h
6480
}
6581

6682
// request is the parsed addressing of a data-plane call.
@@ -127,6 +143,13 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
127143
return
128144
}
129145

146+
if h.previewRoutes != nil && req.resource == "sandboxrunners" && req.verb == "proxy" {
147+
if err := h.previewRoutes.Ensure(r.Context(), instance); err != nil {
148+
http.Error(w, err.Error(), http.StatusServiceUnavailable)
149+
return
150+
}
151+
}
152+
130153
// 5b. Reverse-proxy to the runtime Service the provider owns.
131154
serveProxy(w, r, h.runtime, target, req.callerPath)
132155
}

providers/infrastructure/dataplane/handler_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package dataplane
1818

1919
import (
2020
"context"
21+
"errors"
2122
"io"
2223
"net/http"
2324
"net/http/httptest"
@@ -77,6 +78,19 @@ func (f *fakeRuntime) ControlToken(_ context.Context, namespace, name string) (s
7778
return f.token, nil
7879
}
7980

81+
type fakePreviewRouteManager struct {
82+
err error
83+
84+
calls int
85+
got *unstructured.Unstructured
86+
}
87+
88+
func (f *fakePreviewRouteManager) Ensure(_ context.Context, instance *unstructured.Unstructured) error {
89+
f.calls++
90+
f.got = instance
91+
return f.err
92+
}
93+
8094
func newTestHandler(t *testing.T, ig *fakeInstanceGetter, rt *fakeRuntime) *Handler {
8195
t.Helper()
8296
return NewHandler(ig, &fakeContractGetter{contract: sandboxRunnerContract()}, rt)
@@ -154,6 +168,61 @@ func TestHandlerProxyVerbAppendsCallerPath(t *testing.T) {
154168
}
155169
}
156170

171+
func TestHandlerEnsuresPreviewRouteBeforeProxyingSandboxPreview(t *testing.T) {
172+
var gotPath string
173+
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
174+
gotPath = r.URL.Path
175+
}))
176+
defer upstream.Close()
177+
178+
manager := &fakePreviewRouteManager{}
179+
h := NewHandler(
180+
&fakeInstanceGetter{instance: runnerInstance(testNamespace)},
181+
&fakeContractGetter{contract: sandboxRunnerContract()},
182+
&fakeRuntime{host: upstream.URL},
183+
WithPreviewRouteManager(manager),
184+
)
185+
rec := doRequest(h, http.MethodGet, dataplaneURL("proxy")+"/assets/app.js")
186+
if rec.Code != http.StatusOK {
187+
t.Fatalf("status = %d, want 200", rec.Code)
188+
}
189+
if manager.calls != 1 {
190+
t.Fatalf("preview route ensure calls = %d, want 1", manager.calls)
191+
}
192+
if manager.got == nil || manager.got.GetName() != "kedge-sandbox-a1c31ddaaaa007d4" {
193+
t.Fatalf("preview route manager got instance %#v", manager.got)
194+
}
195+
if gotPath == "" {
196+
t.Fatal("request was not proxied after preview route ensure")
197+
}
198+
}
199+
200+
func TestHandlerReportsPreviewRouteNotReady(t *testing.T) {
201+
h := NewHandler(
202+
&fakeInstanceGetter{instance: runnerInstance(testNamespace)},
203+
&fakeContractGetter{contract: sandboxRunnerContract()},
204+
&fakeRuntime{host: "http://unused"},
205+
WithPreviewRouteManager(&fakePreviewRouteManager{err: previewRouteNotReady("waiting for ResolvedRefs")}),
206+
)
207+
rec := doRequest(h, http.MethodGet, dataplaneURL("proxy"))
208+
if rec.Code != http.StatusServiceUnavailable {
209+
t.Fatalf("status = %d, want 503", rec.Code)
210+
}
211+
}
212+
213+
func TestHandlerFailsClosedWhenPreviewRouteValidationFails(t *testing.T) {
214+
h := NewHandler(
215+
&fakeInstanceGetter{instance: runnerInstance(testNamespace)},
216+
&fakeContractGetter{contract: sandboxRunnerContract()},
217+
&fakeRuntime{host: "http://unused"},
218+
WithPreviewRouteManager(&fakePreviewRouteManager{err: errors.New("unexpected backend")}),
219+
)
220+
rec := doRequest(h, http.MethodGet, dataplaneURL("proxy"))
221+
if rec.Code != http.StatusServiceUnavailable {
222+
t.Fatalf("status = %d, want 503", rec.Code)
223+
}
224+
}
225+
157226
func TestHandlerStatusVerbServedFromCR(t *testing.T) {
158227
h := newTestHandler(t, &fakeInstanceGetter{instance: runnerInstance(testNamespace)}, &fakeRuntime{host: "http://unused"})
159228
rec := doRequest(h, http.MethodGet, dataplaneURL("status"))

0 commit comments

Comments
 (0)