Skip to content

Commit b15eb56

Browse files
adamwggithub-actions[bot]
authored andcommitted
render: Have the context function listen on a TCP port
Previously, the context function (which injects and extracts function pipeline context when rendering) listened on a unix socket. This didn't work in environments where containers don't share a kernel with the CLI (e.g., macOS with Docker Desktop), causing render to time out in such environments. Update the context function to listen on a TCP port. We already handle functions listening on TCP on the host, since this is how the "development" function runtime works. Note that we have to listen on 0.0.0.0 (not localhost/127.0.0.1) to ensure we're reachable from Docker. Fixes #161 Signed-off-by: Adam Wolfe Gordon <awg@upbound.io> (cherry picked from commit e80ffa7)
1 parent 415c94d commit b15eb56

6 files changed

Lines changed: 24 additions & 117 deletions

File tree

cmd/crossplane/render/contextfn/listener.go

Lines changed: 13 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@ package contextfn
1919
import (
2020
"context"
2121
"encoding/json"
22+
"fmt"
2223
"net"
23-
"os"
24-
"path/filepath"
2524
"sync"
2625
"time"
2726

@@ -37,16 +36,10 @@ import (
3736

3837
// Handle is the owner of a running in-process context function.
3938
type Handle struct {
40-
// Target is the gRPC target that dials the function. Set this as the
41-
// FunctionInput address passed to the render engine.
42-
Target string
43-
39+
target string
4440
srv *grpc.Server
4541
fn *server
46-
socketPath string
47-
dir string
4842
stop sync.Once
49-
log logging.Logger
5043
seedInput *runtime.RawExtension
5144
captureInput *runtime.RawExtension
5245
}
@@ -58,9 +51,8 @@ func (h *Handle) Captured() *structpb.Struct {
5851
}
5952

6053
// Start starts an in-process gRPC server that implements the composition
61-
// function RunFunction RPC for context seeding and capture. The server
62-
// listens on a unix-domain socket inside a fresh temp directory. Callers
63-
// must call Handle.Stop when done.
54+
// function RunFunction RPC for context seeding and capture. Callers must call
55+
// Handle.Stop when done.
6456
func Start(ctx context.Context, log logging.Logger, contextData map[string]any) (*Handle, error) {
6557
si, err := json.Marshal(input{Mode: modeSeed})
6658
if err != nil {
@@ -71,51 +63,24 @@ func Start(ctx context.Context, log logging.Logger, contextData map[string]any)
7163
return nil, errors.Wrap(err, "cannot create capture context function input")
7264
}
7365

74-
dir, err := os.MkdirTemp("", "render-ctx-*")
75-
if err != nil {
76-
return nil, errors.Wrap(err, "cannot create temp dir for context function socket")
77-
}
78-
79-
cleanup := func() {
80-
_ = os.RemoveAll(dir)
81-
}
82-
83-
sockPath := filepath.Join(dir, "s")
8466
var lc net.ListenConfig
85-
lis, err := lc.Listen(ctx, "unix", sockPath)
67+
lis, err := lc.Listen(ctx, "tcp", ":0")
8668
if err != nil {
87-
cleanup()
88-
return nil, errors.Wrapf(err, "cannot listen on %q", sockPath)
89-
}
90-
91-
cleanup = func() {
92-
_ = lis.Close()
93-
_ = os.RemoveAll(dir)
94-
}
95-
96-
// In order for processes in Docker containers to connect to the socket, the
97-
// socket must be world-writeable and its containing directory must be
98-
// world-readable.
99-
if err := os.Chmod(dir, 0o755); err != nil { //nolint:gosec // Necessary.
100-
cleanup()
101-
return nil, errors.Wrapf(err, "cannot make socket directory world-readable")
102-
}
103-
if err := os.Chmod(sockPath, 0o777); err != nil { //nolint:gosec // Necessary.
104-
cleanup()
105-
return nil, errors.Wrapf(err, "cannot make socket file writeable")
69+
return nil, errors.Wrap(err, "cannot create listener for context function")
10670
}
10771

10872
srv := grpc.NewServer(grpc.Creds(insecure.NewCredentials()))
10973
fn := newServer(contextData)
11074
fnv1.RegisterFunctionRunnerServiceServer(srv, fn)
11175

76+
addr := lis.Addr().(*net.TCPAddr) //nolint:forcetypeassert // We specified "tcp" above.
77+
11278
h := &Handle{
113-
Target: "unix://" + sockPath,
79+
// Report the target as 127.0.0.1:PORT since the render machinery knows
80+
// how to handle functions listening on loopback.
81+
target: fmt.Sprintf("127.0.0.1:%d", addr.Port),
11482
srv: srv,
11583
fn: fn,
116-
socketPath: sockPath,
117-
dir: dir,
118-
log: log,
11984
seedInput: &runtime.RawExtension{Raw: si},
12085
captureInput: &runtime.RawExtension{Raw: ci},
12186
}
@@ -129,8 +94,8 @@ func Start(ctx context.Context, log logging.Logger, contextData map[string]any)
12994
return h, nil
13095
}
13196

132-
// Stop gracefully stops the function server and removes the socket directory.
133-
// Safe to call multiple times.
97+
// Stop gracefully stops the function server, which closes its listener. Safe to
98+
// call multiple times.
13499
func (h *Handle) Stop() {
135100
h.stop.Do(func() {
136101
done := make(chan struct{})
@@ -143,8 +108,5 @@ func (h *Handle) Stop() {
143108
case <-time.After(5 * time.Second):
144109
h.srv.Stop()
145110
}
146-
if err := os.RemoveAll(h.dir); err != nil {
147-
h.log.Debug("Cannot remove context function socket directory", "dir", h.dir, "error", err)
148-
}
149111
})
150112
}

cmd/crossplane/render/contextfn/listener_test.go

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package contextfn
1818

1919
import (
2020
"context"
21-
"os"
2221
"testing"
2322
"time"
2423

@@ -42,7 +41,7 @@ func TestListenerRoundTrip(t *testing.T) {
4241
}
4342
defer h.Stop()
4443

45-
conn, err := grpc.NewClient(h.Target, grpc.WithTransportCredentials(insecure.NewCredentials()))
44+
conn, err := grpc.NewClient(h.target, grpc.WithTransportCredentials(insecure.NewCredentials()))
4645
if err != nil {
4746
t.Fatalf("grpc.NewClient: %v", err)
4847
}
@@ -61,26 +60,3 @@ func TestListenerRoundTrip(t *testing.T) {
6160
t.Errorf("context (-want +got):\n%s", diff)
6261
}
6362
}
64-
65-
func TestStopRemovesSocket(t *testing.T) {
66-
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
67-
defer cancel()
68-
69-
h, err := Start(ctx, logging.NewNopLogger(), nil)
70-
if err != nil {
71-
t.Fatalf("start: %v", err)
72-
}
73-
74-
if _, err := os.Stat(h.socketPath); err != nil {
75-
t.Fatalf("socket should exist: %v", err)
76-
}
77-
78-
h.Stop()
79-
80-
if _, err := os.Stat(h.socketPath); !os.IsNotExist(err) {
81-
t.Errorf("socket should not exist after Stop, got err=%v", err)
82-
}
83-
84-
// Second Stop is a no-op.
85-
h.Stop()
86-
}

cmd/crossplane/render/contextfn/wire.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ import (
2727
// Mirror the render package's runtime-selection annotations. Duplicated here
2828
// rather than importing cmd/crank/render to avoid an import cycle.
2929
const (
30-
annotationKeyRuntime = "render.crossplane.io/runtime"
31-
annotationValueRuntimeInProcess = "InProcess"
30+
annotationKeyRuntime = "render.crossplane.io/runtime"
31+
annotationValueRuntimeDevelopment = "Development"
32+
annotationKeyRuntimeDevelopmentTarget = "render.crossplane.io/runtime-development-target"
3233
)
3334

3435
// Function returns the Function definition the caller must add to the
@@ -37,8 +38,11 @@ const (
3738
func (h *Handle) Function() pkgv1.Function {
3839
return pkgv1.Function{
3940
ObjectMeta: metav1.ObjectMeta{
40-
Name: FunctionName,
41-
Annotations: map[string]string{annotationKeyRuntime: annotationValueRuntimeInProcess},
41+
Name: FunctionName,
42+
Annotations: map[string]string{
43+
annotationKeyRuntime: annotationValueRuntimeDevelopment,
44+
annotationKeyRuntimeDevelopmentTarget: h.target,
45+
},
4246
},
4347
}
4448
}

cmd/crossplane/render/engine_docker.go

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package render
1919
import (
2020
"context"
2121
"os"
22-
"path/filepath"
2322
"runtime"
2423
"strings"
2524

@@ -133,17 +132,6 @@ func (e *dockerRenderEngine) Render(ctx context.Context, req *renderv1alpha1.Ren
133132
opts = append(opts, docker.RunWithNetworkName(e.network))
134133
}
135134

136-
// Bind-mount the directory of every unix-socket function target into the
137-
// render container at the same path so unix:// targets are reachable.
138-
for _, fn := range getFunctionInputs(req) {
139-
addr := fn.GetAddress()
140-
if !strings.HasPrefix(addr, "unix://") {
141-
continue
142-
}
143-
dir := filepath.Dir(strings.TrimPrefix(addr, "unix://"))
144-
opts = append(opts, docker.RunWithBindMount(dir, dir))
145-
}
146-
147135
e.log.Debug("Running crossplane internal render in Docker", "image", e.image, "network", e.network)
148136

149137
runner := e.runner
@@ -173,16 +161,3 @@ func (e *dockerRenderEngine) Render(ctx context.Context, req *renderv1alpha1.Ren
173161

174162
return rsp, nil
175163
}
176-
177-
// getFunctionInputs returns the FunctionInput list regardless of which oneof
178-
// variant the RenderRequest carries.
179-
func getFunctionInputs(req *renderv1alpha1.RenderRequest) []*renderv1alpha1.FunctionInput {
180-
switch in := req.GetInput().(type) {
181-
case *renderv1alpha1.RenderRequest_Composite:
182-
return in.Composite.GetFunctions()
183-
case *renderv1alpha1.RenderRequest_Operation:
184-
return in.Operation.GetFunctions()
185-
default:
186-
return nil
187-
}
188-
}

cmd/crossplane/render/op/cmd.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,15 +216,10 @@ func (c *Cmd) Run(k *kong.Context, log logging.Logger, sp terminal.SpinnerPrinte
216216
}
217217
defer render.StopFunctionRuntimes(log, fnAddrs)
218218

219-
addrs := fnAddrs.Addresses()
220-
if ctxHandle != nil {
221-
addrs[contextfn.FunctionName] = ctxHandle.Target
222-
}
223-
224219
// Build and execute the render request.
225220
in := render.OperationInputs{
226221
Operation: op,
227-
FunctionAddrs: addrs,
222+
FunctionAddrs: fnAddrs.Addresses(),
228223
RequiredResources: rrs,
229224
RequiredSchemas: rsc,
230225
FunctionCredentials: fcreds,

cmd/crossplane/render/xr/cmd.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -281,16 +281,11 @@ func (c *Cmd) Run(k *kong.Context, log logging.Logger, sp terminal.SpinnerPrinte
281281
}
282282
defer render.StopFunctionRuntimes(log, fnAddrs)
283283

284-
addrs := fnAddrs.Addresses()
285-
if ctxHandle != nil {
286-
addrs[contextfn.FunctionName] = ctxHandle.Target
287-
}
288-
289284
// Build and execute the render request.
290285
in := render.CompositionInputs{
291286
CompositeResource: xr,
292287
Composition: comp,
293-
FunctionAddrs: addrs,
288+
FunctionAddrs: fnAddrs.Addresses(),
294289
ObservedResources: ors,
295290
RequiredResources: rrs,
296291
RequiredSchemas: rsc,

0 commit comments

Comments
 (0)