Skip to content

render: Have the context function listen on a TCP port#163

Open
adamwg wants to merge 1 commit into
crossplane:mainfrom
adamwg:awg/context-tcp
Open

render: Have the context function listen on a TCP port#163
adamwg wants to merge 1 commit into
crossplane:mainfrom
adamwg:awg/context-tcp

Conversation

@adamwg

@adamwg adamwg commented Jun 26, 2026

Copy link
Copy Markdown
Member

Description of your changes

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

I have:

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 crossplane#161

Signed-off-by: Adam Wolfe Gordon <awg@upbound.io>
@adamwg adamwg requested review from a team, jcogilvie and tampakrap as code owners June 26, 2026 21:50
@adamwg adamwg requested review from bobh66 and removed request for a team June 26, 2026 21:50
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The context function listener now uses loopback TCP, publishes its target through function annotations, and render commands pass function addresses through unchanged. The Docker engine no longer adds unix-socket bind mounts, and listener tests were updated for the new target field.

Changes

Loopback TCP context function rendering

Layer / File(s) Summary
TCP listener and handle target
cmd/crossplane/render/contextfn/listener.go, cmd/crossplane/render/contextfn/listener_test.go
Start now serves gRPC on loopback TCP, stores the target on Handle.target, and the listener test uses that field while dropping socket cleanup coverage.
Development runtime annotations
cmd/crossplane/render/contextfn/wire.go
Handle.Function() now sets development runtime annotations and a runtime-development-target value from h.target.
Direct render address wiring
cmd/crossplane/render/op/cmd.go, cmd/crossplane/render/xr/cmd.go
render.OperationInputs and render.CompositionInputs now pass fnAddrs.Addresses() through unchanged instead of overwriting the context function address from the handle.
Docker engine socket handling removal
cmd/crossplane/render/engine_docker.go
The Docker render engine removes unix-socket bind-mount handling and the helper that extracted function inputs.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Suggested reviewers

  • jcogilvie

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Breaking Changes ❌ Error cmd/** renames exported Handle.Target to unexported target and removes render address/socket behavior; no breaking-change label is provided here. Add the breaking-change label, or keep the public field/behavior intact (e.g. preserve Handle.Target and the existing address override).
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is descriptive, under 72 characters, and clearly summarizes the TCP listener change.
Description check ✅ Passed The description is on-topic and explains the Unix-socket-to-TCP fix for macOS Docker Desktop.
Linked Issues check ✅ Passed The code changes match issue #161 by moving the context function off a Unix socket and updating render wiring.
Out of Scope Changes check ✅ Passed I don't see clear unrelated changes; the edits support the TCP context-function fix and its render plumbing.
Feature Gate Requirement ✅ Passed No new API surface or experimental toggle was added; the PR reuses existing Development runtime plumbing to fix context render transport.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
cmd/crossplane/render/contextfn/listener_test.go (1)

33-44: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep the updated listener coverage table-driven.

Thanks for updating the dial target. Since this test now covers the TCP target contract, could you wrap it in the repo’s table-driven args/want shape with a named case/reason so future target cases can be added cleanly? As per path instructions, “**/*_test.go: Enforce table-driven test structure: PascalCase test names (no underscores), args/want pattern, use cmp.Diff with cmpopts.EquateErrors() for error testing.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/crossplane/render/contextfn/listener_test.go` around lines 33 - 44, The
listener round-trip test is not following the repo’s table-driven test pattern.
Update TestListenerRoundTrip in listener_test.go to use a named table case with
args/want fields and a reason/name for the TCP target scenario, so additional
target cases can be added cleanly. Keep the test logic in the existing Start and
grpc.NewClient path, but structure assertions with cmp.Diff and
cmpopts.EquateErrors() where errors are checked, and preserve a PascalCase test
name without underscores.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/crossplane/render/contextfn/listener.go`:
- Line 39: The listener in listener.go is currently binding with an unspecified
address, which exposes the insecure gRPC server on all interfaces despite
advertising a loopback target. Update the listen address used by the listener
setup so it binds to 127.0.0.1 only, and keep the returned target consistent
with that local-only binding in the listener/handle flow.
- Around line 67-81: The context-function listener currently advertises a
loopback target in newHandle while binding with ":"0, which actually listens on
all interfaces. Update the listener setup in create/listener code around
lc.Listen and Handle construction to bind explicitly to 127.0.0.1:0 by default,
or add an explicit configuration path if a non-loopback bind is intended. Keep
the existing target formatting logic in Handle consistent with the actual bind
address so the advertised endpoint matches the listener behavior.

---

Nitpick comments:
In `@cmd/crossplane/render/contextfn/listener_test.go`:
- Around line 33-44: The listener round-trip test is not following the repo’s
table-driven test pattern. Update TestListenerRoundTrip in listener_test.go to
use a named table case with args/want fields and a reason/name for the TCP
target scenario, so additional target cases can be added cleanly. Keep the test
logic in the existing Start and grpc.NewClient path, but structure assertions
with cmp.Diff and cmpopts.EquateErrors() where errors are checked, and preserve
a PascalCase test name without underscores.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2bfab7bc-5f1b-4d8e-93a3-b4b8a6641df8

📥 Commits

Reviewing files that changed from the base of the PR and between 86f5f7a and e80ffa7.

📒 Files selected for processing (6)
  • cmd/crossplane/render/contextfn/listener.go
  • cmd/crossplane/render/contextfn/listener_test.go
  • cmd/crossplane/render/contextfn/wire.go
  • cmd/crossplane/render/engine_docker.go
  • cmd/crossplane/render/op/cmd.go
  • cmd/crossplane/render/xr/cmd.go
💤 Files with no reviewable changes (1)
  • cmd/crossplane/render/engine_docker.go

// FunctionInput address passed to the render engine.
Target string

target string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n 'type Handle struct|Target\s+string|target\s+string' cmd/crossplane/render/contextfn/listener.go
if command -v gh >/dev/null; then
  gh pr view --json labels --jq '.labels[].name'
fi

Repository: crossplane/cli

Length of output: 273


🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline cmd/crossplane/render/contextfn/listener.go --view expanded
printf '\n--- file ---\n'
cat -n cmd/crossplane/render/contextfn/listener.go | sed -n '1,220p'
printf '\n--- references ---\n'
rg -n '\bHandle\b|\bTarget\b|\btarget\b' cmd/crossplane/render/contextfn

Repository: crossplane/cli

Length of output: 6462


Bind the listener to loopback only. lc.Listen(ctx, "tcp", ":0") opens the server on all interfaces even though the handle advertises 127.0.0.1:PORT and uses insecure gRPC. If this is meant to stay local, can we bind 127.0.0.1:0 instead?

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/crossplane/render/contextfn/listener.go` at line 39, The listener in
listener.go is currently binding with an unspecified address, which exposes the
insecure gRPC server on all interfaces despite advertising a loopback target.
Update the listen address used by the listener setup so it binds to 127.0.0.1
only, and keep the returned target consistent with that local-only binding in
the listener/handle flow.

Source: Coding guidelines

Comment on lines +67 to +81
lis, err := lc.Listen(ctx, "tcp", ":0")
if err != nil {
cleanup()
return nil, errors.Wrapf(err, "cannot listen on %q", sockPath)
}

cleanup = func() {
_ = lis.Close()
_ = os.RemoveAll(dir)
}

// In order for processes in Docker containers to connect to the socket, the
// socket must be world-writeable and its containing directory must be
// world-readable.
if err := os.Chmod(dir, 0o755); err != nil { //nolint:gosec // Necessary.
cleanup()
return nil, errors.Wrapf(err, "cannot make socket directory world-readable")
}
if err := os.Chmod(sockPath, 0o777); err != nil { //nolint:gosec // Necessary.
cleanup()
return nil, errors.Wrapf(err, "cannot make socket file writeable")
return nil, errors.Wrap(err, "cannot create listener for context function")
}

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

addr := lis.Addr().(*net.TCPAddr) //nolint:forcetypeassert // We specified "tcp" above.

h := &Handle{
Target: "unix://" + sockPath,
// Report the target as 127.0.0.1:PORT since the render machinery knows
// how to handle functions listening on loopback.
target: fmt.Sprintf("127.0.0.1:%d", addr.Port),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n 'Listen\(ctx, "tcp", ":0"\)|127\.0\.0\.1|host\.docker\.internal|runtime-development-target' cmd/crossplane/render

Repository: crossplane/cli

Length of output: 3519


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the file with line numbers and inspect relevant call sites.
cat -n cmd/crossplane/render/contextfn/listener.go | sed -n '1,180p'
printf '\n---\n'
cat -n cmd/crossplane/render/render.go | sed -n '130,170p'

Repository: crossplane/cli

Length of output: 5826


Bind the context-function listener to loopback by default. The server is advertised as 127.0.0.1:PORT, but ":0" listens on all interfaces. Since the Docker path already rewrites loopback addresses, can this use 127.0.0.1:0 instead? If a non-loopback bind is intentional, make it explicit/configured.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/crossplane/render/contextfn/listener.go` around lines 67 - 81, The
context-function listener currently advertises a loopback target in newHandle
while binding with ":"0, which actually listens on all interfaces. Update the
listener setup in create/listener code around lc.Listen and Handle construction
to bind explicitly to 127.0.0.1:0 by default, or add an explicit configuration
path if a non-loopback bind is intended. Keep the existing target formatting
logic in Handle consistent with the actual bind address so the advertised
endpoint matches the listener behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Context injection/extraction in render does not work with macOS Docker Desktop

1 participant