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") + } + } + } + }) + } +}