From 92196c912811d0ba9ef080815f5a98fb02393d1d Mon Sep 17 00:00:00 2001 From: Jon Tucci Date: Wed, 18 Mar 2026 10:09:24 -0700 Subject: [PATCH] Support CROSSPLANE_DIFF_DOCKER_NETWORK env var for container networking When crossplane-diff runs inside a Docker container (e.g. a GitHub Actions container job), function containers created via the Docker socket land on a different Docker network and are unreachable. This adds support for the CROSSPLANE_DIFF_DOCKER_NETWORK environment variable. When set, the value is applied as the render.crossplane.io/runtime-docker-network annotation on all function resources before they are passed to render.Render(). This tells the Crossplane render runtime to connect function containers to the specified Docker network, making them reachable from the caller. Usage in a GitHub Actions workflow: env: CROSSPLANE_DIFF_DOCKER_NETWORK: ${{ job.container.network }} Depends on: crossplane/crossplane#7216 Signed-off-by: Jon Tucci --- cmd/diff/diffprocessor/function_provider.go | 34 ++++++++++ .../diffprocessor/function_provider_test.go | 68 +++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/cmd/diff/diffprocessor/function_provider.go b/cmd/diff/diffprocessor/function_provider.go index 94e4774..526ef10 100644 --- a/cmd/diff/diffprocessor/function_provider.go +++ b/cmd/diff/diffprocessor/function_provider.go @@ -22,6 +22,7 @@ import ( "crypto/sha256" "encoding/hex" "fmt" + "os" "strings" "time" @@ -48,6 +49,35 @@ type FunctionProvider interface { Cleanup(ctx context.Context) error } +// EnvDockerNetwork is the environment variable that specifies which Docker +// network function containers should join. This is needed when crossplane-diff +// runs inside a Docker container (e.g. a GitHub Actions container job) so that +// function containers are on the same network and reachable via container IP. +const EnvDockerNetwork = "CROSSPLANE_DIFF_DOCKER_NETWORK" + +// annotationRuntimeDockerNetwork is the render annotation that configures the +// Docker network for function containers. +const annotationRuntimeDockerNetwork = "render.crossplane.io/runtime-docker-network" + +// applyDockerNetworkAnnotation sets the Docker network annotation on functions +// if the CROSSPLANE_DIFF_DOCKER_NETWORK environment variable is set. +func applyDockerNetworkAnnotation(fns []pkgv1.Function, log logging.Logger) { + network := os.Getenv(EnvDockerNetwork) + if network == "" { + return + } + + log.Debug("Setting Docker network annotation on functions", "network", network) + + for i := range fns { + if fns[i].Annotations == nil { + fns[i].Annotations = make(map[string]string) + } + + fns[i].Annotations[annotationRuntimeDockerNetwork] = network + } +} + // DefaultFunctionProvider fetches functions from the cluster on each call. // This is appropriate for the xr command where each XR is processed independently. type DefaultFunctionProvider struct { @@ -74,6 +104,8 @@ func (p *DefaultFunctionProvider) GetFunctionsForComposition(comp *apiextensions p.logger.Debug("Fetched functions from pipeline", "composition", comp.GetName(), "count", len(fns)) + applyDockerNetworkAnnotation(fns, p.logger) + return fns, nil } @@ -168,6 +200,8 @@ func (p *CachedFunctionProvider) GetFunctionsForComposition(comp *apiextensionsv // Cache for future calls p.cache[compName] = fns + applyDockerNetworkAnnotation(fns, p.logger) + return fns, nil } diff --git a/cmd/diff/diffprocessor/function_provider_test.go b/cmd/diff/diffprocessor/function_provider_test.go index 89b6800..6763053 100644 --- a/cmd/diff/diffprocessor/function_provider_test.go +++ b/cmd/diff/diffprocessor/function_provider_test.go @@ -17,6 +17,7 @@ limitations under the License. package diffprocessor import ( + "os" "strings" "testing" @@ -671,3 +672,70 @@ func TestGenerateContainerName(t *testing.T) { }) } } + +func TestApplyDockerNetworkAnnotation(t *testing.T) { + tests := map[string]struct { + envValue string + fns []pkgv1.Function + wantNetwork string + }{ + "EnvSet": { + envValue: "github_network_abc123", + fns: []pkgv1.Function{ + {ObjectMeta: metav1.ObjectMeta{Name: "function-1"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "function-2"}}, + }, + wantNetwork: "github_network_abc123", + }, + "EnvNotSet": { + envValue: "", + fns: []pkgv1.Function{ + {ObjectMeta: metav1.ObjectMeta{Name: "function-1"}}, + }, + wantNetwork: "", + }, + "ExistingAnnotations": { + envValue: "my-network", + fns: []pkgv1.Function{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "function-1", + Annotations: map[string]string{ + "existing-key": "existing-value", + }, + }, + }, + }, + wantNetwork: "my-network", + }, + } + + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + if tt.envValue != "" { + t.Setenv(EnvDockerNetwork, tt.envValue) + } else { + os.Unsetenv(EnvDockerNetwork) + } + + logger := tu.TestLogger(t, false) + applyDockerNetworkAnnotation(tt.fns, logger) + + for _, fn := range tt.fns { + got := fn.Annotations[annotationRuntimeDockerNetwork] + if got != tt.wantNetwork { + t.Errorf("function %q: network annotation = %q, want %q", fn.Name, got, tt.wantNetwork) + } + } + + // Verify existing annotations are preserved when env is set + if tt.wantNetwork != "" { + for _, fn := range tt.fns { + if v, ok := fn.Annotations["existing-key"]; ok && v != "existing-value" { + t.Errorf("existing annotation was modified: got %q, want %q", v, "existing-value") + } + } + } + }) + } +}