Repository: kroxylicious/kroxylicious (java)

Issue:
Report downstream bootstrap address
As an application developer I need to know what the bootstrap address of my virtual cluster is so I can enable clients to connect to the cluster. 

Acceptance cirteria:

- On cluster bootstrap address is reported via the status section of the Virtual Cluster CR.

Pull request:
Fix #1829: Report downstream bootstrap address via the VirtualKafkaCluster status section.
### Type of change

_Select the type of your PR_

- Enhancement / new feature

### Description

As an application developer I need to know what the bootstrap address of my virtual cluster is so I can enable clients to connect to the cluster.

This changes adds an ingress array to the VKC's status section, giving the user access to the ingress host + bootstrap port for each defined ingress.

### Additional Context

_Why are you making this pull request?_

### Checklist

_Please go through this checklist and make sure all applicable tasks have been done_

- [x] PR raised from a fork of this repository and made from a branch rather than main. 
- [x] Write tests
- [ ] Update documentation
- [x] Make sure all unit/integration tests pass
- [ ] Make sure all Sonarcloud warnings are addressed or are justifiably ignored.
- [ ] If applicable to the change, [trigger the system test suite](../blob/main/DEV_GUIDE.md#jenkins-pipeline-for-system-tests).  Make sure tests pass.
- [ ] If applicable to the change, [trigger the performance test suite](../blob/main/PERFORMANCE.md#jenkins-pipeline-for-performance). Ensure that any degradations to performance numbers are understood and justified.
- [ ] Ensure the PR references relevant issue(s) so they are closed on merging.
- [ ] For user facing changes, update CHANGELOG.md (remember to include changes affecting the API of the test artefacts too).

> **_NOTE:_**  You must be a member of `@kroxylicious/developers` to trigger the system test and performance test suites.  If you are not part of this group, comment on the PR requesting a trigger, tagging `@kroxylicious/developers`.


Patch:
diff --git a/kroxylicious-operator/src/main/java/io/kroxylicious/kubernetes/operator/VirtualKafkaClusterReconciler.java b/kroxylicious-operator/src/main/java/io/kroxylicious/kubernetes/operator/VirtualKafkaClusterReconciler.java
index 786b6bf0..c9002e5d 100644
--- a/kroxylicious-operator/src/main/java/io/kroxylicious/kubernetes/operator/VirtualKafkaClusterReconciler.java
+++ b/kroxylicious-operator/src/main/java/io/kroxylicious/kubernetes/operator/VirtualKafkaClusterReconciler.java
@@ -18,6 +18,9 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import io.fabric8.kubernetes.api.model.ConfigMap;
+import io.fabric8.kubernetes.api.model.OwnerReference;
+import io.fabric8.kubernetes.api.model.Service;
+import io.fabric8.kubernetes.api.model.ServicePort;
 import io.javaoperatorsdk.operator.api.config.informer.InformerEventSourceConfiguration;
 import io.javaoperatorsdk.operator.api.reconciler.Context;
 import io.javaoperatorsdk.operator.api.reconciler.ErrorStatusUpdateControl;
@@ -30,12 +33,14 @@ import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEven
 
 import io.kroxylicious.kubernetes.api.common.AnyLocalRefBuilder;
 import io.kroxylicious.kubernetes.api.common.Condition;
+import io.kroxylicious.kubernetes.api.common.IngressRefBuilder;
 import io.kroxylicious.kubernetes.api.common.LocalRef;
 import io.kroxylicious.kubernetes.api.v1alpha1.KafkaProxy;
 import io.kroxylicious.kubernetes.api.v1alpha1.KafkaProxyIngress;
 import io.kroxylicious.kubernetes.api.v1alpha1.KafkaService;
 import io.kroxylicious.kubernetes.api.v1alpha1.VirtualKafkaCluster;
 import io.kroxylicious.kubernetes.api.v1alpha1.VirtualKafkaClusterSpec;
+import io.kroxylicious.kubernetes.api.v1alpha1.virtualkafkaclusterstatus.IngressesBuilder;
 import io.kroxylicious.kubernetes.filter.api.v1alpha1.KafkaProtocolFilter;
 import io.kroxylicious.kubernetes.operator.resolver.ClusterResolutionResult;
 import io.kroxylicious.kubernetes.operator.resolver.DependencyResolver;
@@ -56,18 +61,19 @@ public final class VirtualKafkaClusterReconciler implements
         Reconciler<VirtualKafkaCluster> {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(VirtualKafkaClusterReconciler.class);
-    public static final String PROXY_EVENT_SOURCE_NAME = "proxy";
-    public static final String PROXY_CONFIG_STATE_SOURCE_NAME = "proxy-config-state";
-    public static final String SERVICES_EVENT_SOURCE_NAME = "services";
-    public static final String INGRESSES_EVENT_SOURCE_NAME = "ingresses";
-    public static final String FILTERS_EVENT_SOURCE_NAME = "filters";
-    public static final String TRANSITIVELY_REFERENCED_RESOURCES_NOT_FOUND = "TransitivelyReferencedResourcesNotFound";
-    public static final String REFERENCED_RESOURCES_NOT_FOUND = "ReferencedResourcesNotFound";
+    static final String PROXY_EVENT_SOURCE_NAME = "proxy";
+    static final String PROXY_CONFIG_STATE_SOURCE_NAME = "proxy-config-state";
+    static final String SERVICES_EVENT_SOURCE_NAME = "services";
+    static final String INGRESSES_EVENT_SOURCE_NAME = "ingresses";
+    static final String FILTERS_EVENT_SOURCE_NAME = "filters";
+    static final String KUBERNETES_SERVICES_EVENT_SOURCE_NAME = "kubernetesServices";
+    static final String TRANSITIVELY_REFERENCED_RESOURCES_NOT_FOUND = "TransitivelyReferencedResourcesNotFound";
+    static final String REFERENCED_RESOURCES_NOT_FOUND = "ReferencedResourcesNotFound";
     private static final String KAFKA_PROXY_INGRESS_KIND = getKind(KafkaProxyIngress.class);
     private static final String KAFKA_PROXY_KIND = getKind(KafkaProxy.class);
+    private static final String VIRTUAL_KAFKA_CLUSTER_KIND = getKind(VirtualKafkaCluster.class);
     private static final String KAFKA_SERVICE_KIND = getKind(KafkaService.class);
     private static final String KAFKA_PROTOCOL_FILTER_KIND = getKind(KafkaProtocolFilter.class);
-
     private final VirtualKafkaClusterStatusFactory statusFactory;
     private final DependencyResolver resolver;
 
@@ -94,6 +100,28 @@ public final class VirtualKafkaClusterReconciler implements
 
     private UpdateControl<VirtualKafkaCluster> maybeCombineStatusWithClusterConfigMap(VirtualKafkaCluster cluster, Context<VirtualKafkaCluster> context) {
         UpdateControl<VirtualKafkaCluster> updateControl;
+
+        var existingKubernetesServices = context.getSecondaryResources(Service.class)
+                .stream()
+                .collect(Collectors.toMap(ref -> {
+                    var kubeServiceIngressOwner = Optional.ofNullable(ref)
+                            .flatMap(service -> extractOwnerRefFromKubernetesService(service, KAFKA_PROXY_INGRESS_KIND))
+                            .orElseThrow();
+
+                    return new IngressRefBuilder().withName(kubeServiceIngressOwner.getName()).build();
+                }, Function.identity()));
+
+        var ingresses = cluster.getSpec().getIngressRefs()
+                .stream()
+                .filter(existingKubernetesServices::containsKey)
+                .map(ingressRef -> {
+                    var kubenetesService = existingKubernetesServices.get(ingressRef);
+                    var builder = new IngressesBuilder();
+                    builder.withName(ingressRef.getName());
+                    builder.withBootstrapServer(getBootstrapServer(kubenetesService));
+                    return builder.build();
+                }).toList();
+
         updateControl = context
                 .getSecondaryResource(ConfigMap.class)
                 .flatMap(cm -> Optional.ofNullable(cm.getData()))
@@ -102,18 +130,35 @@ public final class VirtualKafkaClusterReconciler implements
                 .map(patch -> {
                     var rr = ResourceState.of(statusFactory.newTrueCondition(cluster, Condition.Type.ResolvedRefs));
                     var acc = ResourceState.fromList(patch.getStatus().getConditions());
-                    return statusFactory.clusterStatusPatch(cluster, rr.replacementFor(acc));
+                    return statusFactory.clusterStatusPatch(cluster, rr.replacementFor(acc), ingresses);
                 })
                 .map(UpdateControl::patchStatus)
                 .orElse(UpdateControl.noUpdate());
         return updateControl;
     }
 
+    private String getBootstrapServer(Service kubenetesService) {
+        var metadata = kubenetesService.getMetadata();
+        var bootstrapPort = kubenetesService.getSpec().getPorts().stream()
+                .map(ServicePort::getPort)
+                .findFirst()
+                .orElseThrow();
+        return metadata.getName() + "." + metadata.getNamespace() + ".svc.cluster.local:" + bootstrapPort;
+    }
+
+    private Optional<OwnerReference> extractOwnerRefFromKubernetesService(Service service, String ownerKind) {
+        return service.getMetadata()
+                .getOwnerReferences()
+                .stream()
+                .filter(or -> ownerKind.equals(or.getKind()))
+                .findFirst();
+    }
+
     private UpdateControl<VirtualKafkaCluster> handleResolutionProblems(VirtualKafkaCluster cluster,
                                                                         ClusterResolutionResult clusterResolutionResult) {
         UpdateControl<VirtualKafkaCluster> updateControl;
         LocalRef<VirtualKafkaCluster> clusterRef = toLocalRef(cluster);
-        var unresovedIngressProxies = clusterResolutionResult.findDanglingReferences(KAFKA_PROXY_INGRESS_KIND, KAFKA_PROXY_KIND).collect(Collectors.toSet());
+        var unresolvedIngressProxies = clusterResolutionResult.findDanglingReferences(KAFKA_PROXY_INGRESS_KIND, KAFKA_PROXY_KIND).collect(Collectors.toSet());
         if (clusterResolutionResult.anyDependenciesNotFoundFor(clusterRef)) {
             Stream<String> proxyMsg = refsMessage("spec.proxyRef references ", cluster,
                     clusterResolutionResult.findDanglingReferences(clusterRef, KAFKA_PROXY_KIND));
@@ -126,7 +171,7 @@ public final class VirtualKafkaClusterReconciler implements
             updateControl = UpdateControl.patchStatus(statusFactory.newFalseConditionStatusPatch(cluster, Condition.Type.ResolvedRefs, Condition.REASON_REFS_NOT_FOUND,
                     joiningMessages(proxyMsg, serviceMsg, ingressMsg, filterMsg)));
         }
-        else if (clusterResolutionResult.anyResolvedRefsConditionsFalse() || !unresovedIngressProxies.isEmpty()) {
+        else if (clusterResolutionResult.anyResolvedRefsConditionsFalse() || !unresolvedIngressProxies.isEmpty()) {
             Stream<String> serviceMsg = refsMessage("spec.targetKafkaServiceRef references ", cluster,
                     clusterResolutionResult.findResourcesWithResolvedRefsFalse(KAFKA_SERVICE_KIND));
             Stream<String> ingressMsg = refsMessage("spec.ingressRefs references ", cluster,
@@ -227,12 +272,30 @@ public final class VirtualKafkaClusterReconciler implements
                         cluster -> cluster.getSpec().getFilterRefs()))
                 .build();
 
+        InformerEventSourceConfiguration<Service> clusterToKubeService = InformerEventSourceConfiguration.from(
+                Service.class,
+                VirtualKafkaCluster.class)
+                .withName(KUBERNETES_SERVICES_EVENT_SOURCE_NAME)
+                .withPrimaryToSecondaryMapper((VirtualKafkaCluster cluster) -> {
+                    var name = cluster.getMetadata().getName();
+                    return cluster.getSpec().getIngressRefs()
+                            .stream()
+                            .flatMap(ir -> ResourcesUtil.localRefAsResourceId(cluster, new AnyLocalRefBuilder().withName(name + "-" + ir.getName()).build()).stream())
+                            .collect(Collectors.toSet());
+                })
+                .withSecondaryToPrimaryMapper(kubenetesService -> Optional.of(kubenetesService)
+                        .flatMap(service -> extractOwnerRefFromKubernetesService(service, VIRTUAL_KAFKA_CLUSTER_KIND))
+                        .map(ownerRef -> new ResourceID(ownerRef.getName(), kubenetesService.getMetadata().getNamespace()))
+                        .map(Set::of).orElse(Set.of()))
+                .build();
+
         return List.of(
                 new InformerEventSource<>(clusterToProxy, context),
                 new InformerEventSource<>(clusterToProxyConfigState, context),
                 new InformerEventSource<>(clusterToIngresses, context),
                 new InformerEventSource<>(clusterToService, context),
-                new InformerEventSource<>(clusterToFilters, context));
+                new InformerEventSource<>(clusterToFilters, context),
+                new InformerEventSource<>(clusterToKubeService, context));
     }
 
     @Override
diff --git a/kroxylicious-operator/src/main/java/io/kroxylicious/kubernetes/operator/VirtualKafkaClusterStatusFactory.java b/kroxylicious-operator/src/main/java/io/kroxylicious/kubernetes/operator/VirtualKafkaClusterStatusFactory.java
index 5b1d3a99..430e703e 100644
--- a/kroxylicious-operator/src/main/java/io/kroxylicious/kubernetes/operator/VirtualKafkaClusterStatusFactory.java
+++ b/kroxylicious-operator/src/main/java/io/kroxylicious/kubernetes/operator/VirtualKafkaClusterStatusFactory.java
@@ -14,6 +14,7 @@ import io.kroxylicious.kubernetes.api.common.Condition;
 import io.kroxylicious.kubernetes.api.v1alpha1.VirtualKafkaCluster;
 import io.kroxylicious.kubernetes.api.v1alpha1.VirtualKafkaClusterBuilder;
 import io.kroxylicious.kubernetes.api.v1alpha1.VirtualKafkaClusterStatus;
+import io.kroxylicious.kubernetes.api.v1alpha1.virtualkafkaclusterstatus.Ingresses;
 
 public class VirtualKafkaClusterStatusFactory extends StatusFactory<VirtualKafkaCluster> {
 
@@ -22,7 +23,7 @@ public class VirtualKafkaClusterStatusFactory extends StatusFactory<VirtualKafka
     }
 
     VirtualKafkaCluster clusterStatusPatch(VirtualKafkaCluster observedIngress,
-                                           ResourceState condition) {
+                                           ResourceState condition, List<Ingresses> ingresses) {
         // @formatter:off
         return new VirtualKafkaClusterBuilder()
                 .withNewMetadata()
@@ -33,6 +34,7 @@ public class VirtualKafkaClusterStatusFactory extends StatusFactory<VirtualKafka
                 .withNewStatus()
                     .withObservedGeneration(ResourcesUtil.generation(observedIngress))
                     .withConditions(ResourceState.newConditions(Optional.ofNullable(observedIngress.getStatus()).map(VirtualKafkaClusterStatus::getConditions).orElse(List.of()), condition))
+                    .withIngresses(ingresses)
                 .endStatus()
                 .build();
         // @formatter:on
@@ -43,7 +45,7 @@ public class VirtualKafkaClusterStatusFactory extends StatusFactory<VirtualKafka
                                                        Condition.Type type,
                                                        Exception e) {
         Condition unknownCondition = newUnknownCondition(observedFilter, type, e);
-        return clusterStatusPatch(observedFilter, ResourceState.of(unknownCondition));
+        return clusterStatusPatch(observedFilter, ResourceState.of(unknownCondition), List.of());
     }
 
     @Override
@@ -52,13 +54,13 @@ public class VirtualKafkaClusterStatusFactory extends StatusFactory<VirtualKafka
                                                      String reason,
                                                      String message) {
         Condition falseCondition = newFalseCondition(observedProxy, type, reason, message);
-        return clusterStatusPatch(observedProxy, ResourceState.of(falseCondition));
+        return clusterStatusPatch(observedProxy, ResourceState.of(falseCondition), List.of());
     }
 
     @Override
     VirtualKafkaCluster newTrueConditionStatusPatch(VirtualKafkaCluster observedProxy,
                                                     Condition.Type type) {
         Condition trueCondition = newTrueCondition(observedProxy, type);
-        return clusterStatusPatch(observedProxy, ResourceState.of(trueCondition));
+        return clusterStatusPatch(observedProxy, ResourceState.of(trueCondition), List.of());
     }
 }
diff --git a/kroxylicious-operator/src/main/java/io/kroxylicious/kubernetes/operator/model/ingress/ClusterIPIngressDefinition.java b/kroxylicious-operator/src/main/java/io/kroxylicious/kubernetes/operator/model/ingress/ClusterIPIngressDefinition.java
index ce8b5d0a..383b80ba 100644
--- a/kroxylicious-operator/src/main/java/io/kroxylicious/kubernetes/operator/model/ingress/ClusterIPIngressDefinition.java
+++ b/kroxylicious-operator/src/main/java/io/kroxylicious/kubernetes/operator/model/ingress/ClusterIPIngressDefinition.java
@@ -27,17 +27,20 @@ import io.kroxylicious.proxy.config.NamedRange;
 import io.kroxylicious.proxy.config.PortIdentifiesNodeIdentificationStrategy;
 import io.kroxylicious.proxy.config.VirtualClusterGateway;
 import io.kroxylicious.proxy.service.HostPort;
+import io.kroxylicious.proxy.tag.VisibleForTesting;
 
 import static io.kroxylicious.kubernetes.operator.Labels.standardLabels;
 import static io.kroxylicious.kubernetes.operator.ResourcesUtil.name;
 import static io.kroxylicious.kubernetes.operator.ResourcesUtil.namespace;
 import static java.lang.Math.toIntExact;
 
-record ClusterIPIngressDefinition(KafkaProxyIngress resource, VirtualKafkaCluster cluster, KafkaProxy primary,
-                                  List<NodeIdRanges> nodeIdRanges)
+public record ClusterIPIngressDefinition(KafkaProxyIngress resource,
+                                         VirtualKafkaCluster cluster,
+                                         KafkaProxy primary,
+                                         List<NodeIdRanges> nodeIdRanges)
         implements IngressDefinition {
 
-    ClusterIPIngressDefinition {
+    public ClusterIPIngressDefinition {
         Objects.requireNonNull(resource);
         Objects.requireNonNull(cluster);
         Objects.requireNonNull(primary);
@@ -77,6 +80,8 @@ record ClusterIPIngressDefinition(KafkaProxyIngress resource, VirtualKafkaCluste
                     .withNamespace(namespace(definition.cluster))
                     .addToLabels(standardLabels(definition.primary))
                     .addNewOwnerReferenceLike(ResourcesUtil.newOwnerReferenceTo(definition.primary)).endOwnerReference()
+                    .addNewOwnerReferenceLike(ResourcesUtil.newOwnerReferenceTo(definition.cluster)).endOwnerReference()
+                    .addNewOwnerReferenceLike(ResourcesUtil.newOwnerReferenceTo(definition.resource)).endOwnerReference()
                     .endMetadata()
                     .withNewSpec()
                     .withSelector(ProxyDeploymentDependentResource.podLabels(definition.primary));
@@ -117,7 +122,8 @@ record ClusterIPIngressDefinition(KafkaProxyIngress resource, VirtualKafkaCluste
         }
     }
 
-    private static String serviceName(VirtualKafkaCluster cluster, KafkaProxyIngress resource) {
+    @VisibleForTesting
+    public static String serviceName(VirtualKafkaCluster cluster, KafkaProxyIngress resource) {
         Objects.requireNonNull(cluster);
         Objects.requireNonNull(resource);
         return name(cluster) + "-" + name(resource);
diff --git a/kroxylicious-operator/src/main/resources/META-INF/fabric8/virtualkafkaclusters.kroxylicious.io-v1.yml b/kroxylicious-operator/src/main/resources/META-INF/fabric8/virtualkafkaclusters.kroxylicious.io-v1.yml
index a518db0d..8d1d7923 100644
--- a/kroxylicious-operator/src/main/resources/META-INF/fabric8/virtualkafkaclusters.kroxylicious.io-v1.yml
+++ b/kroxylicious-operator/src/main/resources/META-INF/fabric8/virtualkafkaclusters.kroxylicious.io-v1.yml
@@ -143,4 +143,21 @@ spec:
                       - observedGeneration
                       - reason
                       - message
-                      - type
\ No newline at end of file
+                      - type
+                ingresses:
+                  description: -|
+                    Lists the ingresses that have been associated with the virtual cluster.
+                  type: array
+                  default: []
+                  items:
+                    type: object
+                    required: [ "name" ]
+                    properties:
+                      name:
+                        description: The name of the ingress.
+                        type: string
+                      bootstrapServer:
+                        description: -|
+                          The address (host and port) that a Kafka client would use make its initial connection to the
+                          virtual cluster. This property will be absent if the address is not yet available.
+                        type: string


Test patch:
diff --git a/kroxylicious-operator/src/test/java/io/kroxylicious/kubernetes/operator/VirtualKafkaClusterReconcilerIT.java b/kroxylicious-operator/src/test/java/io/kroxylicious/kubernetes/operator/VirtualKafkaClusterReconcilerIT.java
index 003ffc1e..358505cb 100644
--- a/kroxylicious-operator/src/test/java/io/kroxylicious/kubernetes/operator/VirtualKafkaClusterReconcilerIT.java
+++ b/kroxylicious-operator/src/test/java/io/kroxylicious/kubernetes/operator/VirtualKafkaClusterReconcilerIT.java
@@ -11,6 +11,7 @@ import java.time.Duration;
 import java.util.List;
 import java.util.Map;
 
+import org.assertj.core.api.InstanceOfAssertFactories;
 import org.awaitility.core.ConditionFactory;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeAll;
@@ -33,6 +34,8 @@ import io.kroxylicious.kubernetes.api.v1alpha1.KafkaService;
 import io.kroxylicious.kubernetes.api.v1alpha1.KafkaServiceBuilder;
 import io.kroxylicious.kubernetes.api.v1alpha1.VirtualKafkaCluster;
 import io.kroxylicious.kubernetes.api.v1alpha1.VirtualKafkaClusterBuilder;
+import io.kroxylicious.kubernetes.api.v1alpha1.VirtualKafkaClusterStatus;
+import io.kroxylicious.kubernetes.api.v1alpha1.virtualkafkaclusterstatus.Ingresses;
 import io.kroxylicious.kubernetes.filter.api.v1alpha1.KafkaProtocolFilter;
 import io.kroxylicious.kubernetes.filter.api.v1alpha1.KafkaProtocolFilterBuilder;
 import io.kroxylicious.kubernetes.operator.assertj.ConditionListAssert;
@@ -260,6 +263,49 @@ class VirtualKafkaClusterReconcilerIT {
         assertAllConditionsTrue(clusterBar);
     }
 
+    @Test
+    void shouldReportIngressClusterIpBootstrap() {
+        // Given
+        testActor.create(kafkaProxy(PROXY_A));
+        testActor.create(kafkaService(SERVICE_H));
+        var cluster = cluster(CLUSTER_BAR, PROXY_A, INGRESS_D, SERVICE_H, null);
+        var ingress = clusterIpIngress(INGRESS_D, PROXY_A);
+        testActor.create(ingress);
+
+        // When
+        VirtualKafkaCluster clusterBar = testActor.create(cluster);
+
+        // Then
+        assertBootstrapServerPopulated(clusterBar, ingress, "bar-cluster-ingress-d.%s.svc.cluster.local:9292");
+    }
+
+    @Test
+    void shouldReportIngressClusterIpBootstrapWhenIngressInitiallyAbsent() {
+        // Given
+        testActor.create(kafkaProxy(PROXY_A));
+        testActor.create(kafkaService(SERVICE_H));
+        var cluster = cluster(CLUSTER_BAR, PROXY_A, INGRESS_D, SERVICE_H, null);
+        var ingress = clusterIpIngress(INGRESS_D, PROXY_A);
+
+        VirtualKafkaCluster clusterBar = testActor.create(cluster);
+
+        AWAIT.alias("ClusterStatusBootstrapNotPresent").untilAsserted(() -> {
+            var vkc = testActor.resources(VirtualKafkaCluster.class)
+                    .withName(ResourcesUtil.name(clusterBar)).get();
+            VirtualKafkaClusterStatus status = vkc.getStatus();
+            assertThat(status)
+                    .isNotNull()
+                    .extracting(VirtualKafkaClusterStatus::getIngresses, InstanceOfAssertFactories.list(Ingresses.class))
+                    .isEmpty();
+        });
+
+        // When
+        testActor.create(ingress);
+
+        // Then
+        assertBootstrapServerPopulated(clusterBar, ingress, "bar-cluster-ingress-d.%s.svc.cluster.local:9292");
+    }
+
     private VirtualKafkaCluster cluster(String clusterName, String proxyName, String ingressName, String serviceName, @Nullable String filterName) {
         return cluster(clusterName, proxyName, List.of(ingressName), serviceName, filterName);
     }
@@ -339,6 +385,22 @@ class VirtualKafkaClusterReconcilerIT {
         });
     }
 
+    private void assertBootstrapServerPopulated(VirtualKafkaCluster clusterBar, KafkaProxyIngress ingress, String expectedBootstrapServer) {
+        AWAIT.alias("ClusterStatusBootstrap").untilAsserted(() -> {
+            var vkc = testActor.resources(VirtualKafkaCluster.class)
+                    .withName(ResourcesUtil.name(clusterBar)).get();
+            var status = vkc.getStatus();
+            assertThat(status)
+                    .isNotNull()
+                    .extracting(VirtualKafkaClusterStatus::getIngresses, InstanceOfAssertFactories.list(Ingresses.class))
+                    .singleElement()
+                    .satisfies(i -> {
+                        assertThat(i.getName()).isEqualTo(ResourcesUtil.name(ingress));
+                        assertThat(i.getBootstrapServer()).isEqualTo(expectedBootstrapServer.formatted(extension.getNamespace()));
+                    });
+        });
+    }
+
     KafkaProxy kafkaProxy(String name) {
         // @formatter:off
         return new KafkaProxyBuilder()
diff --git a/kroxylicious-operator/src/test/java/io/kroxylicious/kubernetes/operator/VirtualKafkaClusterReconcilerTest.java b/kroxylicious-operator/src/test/java/io/kroxylicious/kubernetes/operator/VirtualKafkaClusterReconcilerTest.java
index d73796ad..8a70de80 100644
--- a/kroxylicious-operator/src/test/java/io/kroxylicious/kubernetes/operator/VirtualKafkaClusterReconcilerTest.java
+++ b/kroxylicious-operator/src/test/java/io/kroxylicious/kubernetes/operator/VirtualKafkaClusterReconcilerTest.java
@@ -14,6 +14,7 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.function.BiConsumer;
 
+import org.assertj.core.api.InstanceOfAssertFactories;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
@@ -21,6 +22,9 @@ import org.junit.jupiter.params.provider.MethodSource;
 
 import io.fabric8.kubernetes.api.model.ConfigMap;
 import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
+import io.fabric8.kubernetes.api.model.Service;
+import io.fabric8.kubernetes.api.model.ServiceBuilder;
+import io.fabric8.kubernetes.api.model.ServicePortBuilder;
 import io.javaoperatorsdk.operator.api.reconciler.Context;
 
 import io.kroxylicious.kubernetes.api.common.Condition;
@@ -32,6 +36,8 @@ import io.kroxylicious.kubernetes.api.v1alpha1.KafkaService;
 import io.kroxylicious.kubernetes.api.v1alpha1.KafkaServiceBuilder;
 import io.kroxylicious.kubernetes.api.v1alpha1.VirtualKafkaCluster;
 import io.kroxylicious.kubernetes.api.v1alpha1.VirtualKafkaClusterBuilder;
+import io.kroxylicious.kubernetes.api.v1alpha1.VirtualKafkaClusterStatus;
+import io.kroxylicious.kubernetes.api.v1alpha1.virtualkafkaclusterstatus.Ingresses;
 import io.kroxylicious.kubernetes.filter.api.v1alpha1.KafkaProtocolFilter;
 import io.kroxylicious.kubernetes.filter.api.v1alpha1.KafkaProtocolFilterBuilder;
 import io.kroxylicious.kubernetes.operator.assertj.ConditionListAssert;
@@ -40,6 +46,7 @@ import io.kroxylicious.kubernetes.operator.resolver.DependencyResolver;
 
 import edu.umd.cs.findbugs.annotations.NonNull;
 
+import static io.kroxylicious.kubernetes.operator.model.ingress.ClusterIPIngressDefinition.serviceName;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -50,36 +57,39 @@ class VirtualKafkaClusterReconcilerTest {
     private static final VirtualKafkaClusterStatusFactory STATUS_FACTORY = new VirtualKafkaClusterStatusFactory(TEST_CLOCK);
 
     public static final String PROXY_NAME = "my-proxy";
+    public static final String NAMESPACE = "my-namespace";
+
+    // @formatter:off
     public static final VirtualKafkaCluster CLUSTER_NO_FILTERS = new VirtualKafkaClusterBuilder()
             .withNewMetadata()
-            .withName("foo")
-            .withNamespace("my-namespace")
-            .withGeneration(42L)
+                .withName("foo")
+                .withNamespace(NAMESPACE)
+                .withGeneration(42L)
             .endMetadata()
             .withNewSpec()
-            .withNewProxyRef()
-            .withName(PROXY_NAME)
-            .endProxyRef()
-            .addNewIngressRef()
-            .withName("my-ingress")
-            .endIngressRef()
-            .withNewTargetKafkaServiceRef()
-            .withName("my-kafka")
-            .endTargetKafkaServiceRef()
+                .withNewProxyRef()
+                    .withName(PROXY_NAME)
+                .endProxyRef()
+                .addNewIngressRef()
+                    .withName("my-ingress")
+                .endIngressRef()
+                .withNewTargetKafkaServiceRef()
+                    .withName("my-kafka")
+                .endTargetKafkaServiceRef()
             .endSpec()
             .build();
     public static final VirtualKafkaCluster CLUSTER_ONE_FILTER = new VirtualKafkaClusterBuilder(CLUSTER_NO_FILTERS)
             .editSpec()
-            .addNewFilterRef()
-            .withName("my-filter")
-            .endFilterRef()
+                .addNewFilterRef()
+                    .withName("my-filter")
+                .endFilterRef()
             .endSpec()
             .build();
 
     public static final KafkaProxy PROXY = new KafkaProxyBuilder()
             .withNewMetadata()
-            .withName(PROXY_NAME)
-            .withGeneration(101L)
+                .withName(PROXY_NAME)
+                .withGeneration(101L)
             .endMetadata()
             .withNewSpec()
             .endSpec()
@@ -87,8 +97,8 @@ class VirtualKafkaClusterReconcilerTest {
 
     public static final KafkaService SERVICE = new KafkaServiceBuilder()
             .withNewMetadata()
-            .withName("my-kafka")
-            .withGeneration(201L)
+                .withName("my-kafka")
+              .withGeneration(201L)
             .endMetadata()
             .withNewSpec()
             .endSpec()
@@ -96,20 +106,31 @@ class VirtualKafkaClusterReconcilerTest {
 
     public static final KafkaProxyIngress INGRESS = new KafkaProxyIngressBuilder()
             .withNewMetadata()
-            .withName("my-ingress")
-            .withGeneration(301L)
+                .withName("my-ingress")
+             .withGeneration(301L)
             .endMetadata()
             .withNewSpec()
-            .withNewProxyRef().withName(PROXY_NAME).endProxyRef()
+             .withNewProxyRef().withName(PROXY_NAME).endProxyRef()
             .endSpec()
             .build();
 
     public static final KafkaProtocolFilter FILTER_MY_FILTER = new KafkaProtocolFilterBuilder()
             .withNewMetadata()
-            .withName("my-filter")
-            .withGeneration(401L)
+                .withName("my-filter")
+                .withGeneration(401L)
+            .endMetadata()
+            .withNewSpec()
+            .endSpec()
+            .build();
+    public static final Service KUBERNETES_INGRESS_SERVICES = new ServiceBuilder().
+            withNewMetadata()
+                .withName(serviceName(CLUSTER_NO_FILTERS, INGRESS))
+                .withNamespace(NAMESPACE)
+                .addNewOwnerReferenceLike(ResourcesUtil.newOwnerReferenceTo(CLUSTER_NO_FILTERS)).endOwnerReference()
+                .addNewOwnerReferenceLike(ResourcesUtil.newOwnerReferenceTo(INGRESS)).endOwnerReference()
             .endMetadata()
             .withNewSpec()
+                .addToPorts(new ServicePortBuilder().withName("port").withPort(9082).build())
             .endSpec()
             .build();
     // @formatter:on
@@ -330,4 +351,65 @@ class VirtualKafkaClusterReconcilerTest {
                 .hasLastTransitionTime(TEST_CLOCK.instant());
 
     }
+
+    @Test
+    void shouldSetBootstrapForClusterIPIngress() {
+        // given
+        var reconciler = new VirtualKafkaClusterReconciler(TEST_CLOCK, DependencyResolver.create());
+
+        Context<VirtualKafkaCluster> context = mock(Context.class);
+
+        when(context.getSecondaryResources(KafkaProxy.class)).thenReturn(Set.of(PROXY));
+        when(context.getSecondaryResource(ConfigMap.class)).thenReturn(Optional.of(buildProxyConfigMapWithPatch(CLUSTER_NO_FILTERS)));
+        when(context.getSecondaryResources(KafkaService.class)).thenReturn(Set.of(SERVICE));
+        when(context.getSecondaryResources(KafkaProxyIngress.class)).thenReturn(Set.of(INGRESS));
+        when(context.getSecondaryResources(KafkaProtocolFilter.class)).thenReturn(Set.of());
+        when(context.getSecondaryResources(Service.class)).thenReturn(Set.of(KUBERNETES_INGRESS_SERVICES));
+
+        // when
+        var update = reconciler.reconcile(CLUSTER_NO_FILTERS, context);
+
+        // then
+        assertThat(update).isNotNull();
+        assertThat(update.isPatchStatus()).isTrue();
+        assertThat(update.getResource())
+                .isPresent()
+                .get()
+                .satisfies(r -> assertThat(r.getStatus())
+                        .extracting(VirtualKafkaClusterStatus::getIngresses, InstanceOfAssertFactories.list(Ingresses.class))
+                        .singleElement()
+                        .satisfies(ingress -> {
+                            assertThat(ingress.getName()).isEqualTo(INGRESS.getMetadata().getName());
+                            assertThat(ingress.getBootstrapServer()).isEqualTo("foo-my-ingress.my-namespace.svc.cluster.local:9082");
+                        }));
+
+    }
+
+    @Test
+    void shouldOmitIngressIfKubernetesServiceNotPresent() {
+        // given
+        var reconciler = new VirtualKafkaClusterReconciler(TEST_CLOCK, DependencyResolver.create());
+
+        Context<VirtualKafkaCluster> context = mock(Context.class);
+
+        when(context.getSecondaryResources(KafkaProxy.class)).thenReturn(Set.of(PROXY));
+        when(context.getSecondaryResource(ConfigMap.class)).thenReturn(Optional.of(buildProxyConfigMapWithPatch(CLUSTER_NO_FILTERS)));
+        when(context.getSecondaryResources(KafkaService.class)).thenReturn(Set.of(SERVICE));
+        when(context.getSecondaryResources(KafkaProxyIngress.class)).thenReturn(Set.of(INGRESS));
+        when(context.getSecondaryResources(KafkaProtocolFilter.class)).thenReturn(Set.of());
+        when(context.getSecondaryResources(Service.class)).thenReturn(Set.of());
+
+        // when
+        var update = reconciler.reconcile(CLUSTER_NO_FILTERS, context);
+
+        // then
+        assertThat(update).isNotNull();
+        assertThat(update.isPatchStatus()).isTrue();
+        assertThat(update.getResource())
+                .isPresent()
+                .get()
+                .satisfies(r -> assertThat(r.getStatus())
+                        .extracting(VirtualKafkaClusterStatus::getIngresses, InstanceOfAssertFactories.list(Ingresses.class))
+                        .isEmpty());
+    }
 }
diff --git a/kroxylicious-operator/src/test/java/io/kroxylicious/kubernetes/operator/model/ingress/ClusterIPIngressDefinitionTest.java b/kroxylicious-operator/src/test/java/io/kroxylicious/kubernetes/operator/model/ingress/ClusterIPIngressDefinitionTest.java
index 8a78bb43..a8deb38f 100644
--- a/kroxylicious-operator/src/test/java/io/kroxylicious/kubernetes/operator/model/ingress/ClusterIPIngressDefinitionTest.java
+++ b/kroxylicious-operator/src/test/java/io/kroxylicious/kubernetes/operator/model/ingress/ClusterIPIngressDefinitionTest.java
@@ -151,12 +151,24 @@ class ClusterIPIngressDefinitionTest {
                 assertThat(metadata.getNamespace()).isEqualTo(NAMESPACE);
                 assertThat(metadata.getName()).isEqualTo(CLUSTER_NAME + "-" + INGRESS_NAME);
                 assertThat(metadata.getLabels()).containsExactlyEntriesOf(orderedServiceLabels);
-                assertThat(metadata.getOwnerReferences()).hasSize(1).singleElement().satisfies(ownerRef -> {
-                    assertThat(ownerRef.getKind()).isEqualTo("KafkaProxy");
-                    assertThat(ownerRef.getApiVersion()).isEqualTo("kroxylicious.io/v1alpha1");
-                    assertThat(ownerRef.getName()).isEqualTo(PROXY_NAME);
-                    assertThat(ownerRef.getUid()).isEqualTo(PROXY_UID);
-                });
+                assertThat(metadata.getOwnerReferences())
+                        .satisfiesExactlyInAnyOrder(
+                                ownerRef -> {
+                                    assertThat(ownerRef.getKind()).isEqualTo("KafkaProxy");
+                                    assertThat(ownerRef.getApiVersion()).isEqualTo("kroxylicious.io/v1alpha1");
+                                    assertThat(ownerRef.getName()).isEqualTo(PROXY_NAME);
+                                    assertThat(ownerRef.getUid()).isEqualTo(PROXY_UID);
+                                },
+                                ownerRef -> {
+                                    assertThat(ownerRef.getKind()).isEqualTo("VirtualKafkaCluster");
+                                    assertThat(ownerRef.getApiVersion()).isEqualTo("kroxylicious.io/v1alpha1");
+                                    assertThat(ownerRef.getName()).isEqualTo(CLUSTER_NAME);
+                                },
+                                ownerRef -> {
+                                    assertThat(ownerRef.getKind()).isEqualTo("KafkaProxyIngress");
+                                    assertThat(ownerRef.getApiVersion()).isEqualTo("kroxylicious.io/v1alpha1");
+                                    assertThat(ownerRef.getName()).isEqualTo(INGRESS_NAME);
+                                });
             });
         });
     }
diff --git a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/clusterref-resource-not-found/out-ConfigMap-example-config-state.yaml b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/clusterref-resource-not-found/out-ConfigMap-example-config-state.yaml
index 1ecd826d..be42ea75 100644
--- a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/clusterref-resource-not-found/out-ConfigMap-example-config-state.yaml
+++ b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/clusterref-resource-not-found/out-ConfigMap-example-config-state.yaml
@@ -35,6 +35,7 @@ data:
         reason: "Invalid"
         message: "Resource kafkaservice.kroxylicious.io/barref in namespace 'proxy-ns'\
           \ was not found."
+      ingresses: []
       observedGeneration: 4
   cluster-foo: |-
     ---
@@ -49,4 +50,5 @@ data:
         lastTransitionTime: "1970-01-01T00:00:00Z"
         reason: "Invalid"
         message: "Ingress(es) [cluster-ip] of cluster conflicts with another ingress"
+      ingresses: []
       observedGeneration: 4
\ No newline at end of file
diff --git a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/colliding-secret-interpolation/out-ConfigMap-example-config-state.yaml b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/colliding-secret-interpolation/out-ConfigMap-example-config-state.yaml
index 830ef574..0e2719f5 100644
--- a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/colliding-secret-interpolation/out-ConfigMap-example-config-state.yaml
+++ b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/colliding-secret-interpolation/out-ConfigMap-example-config-state.yaml
@@ -34,4 +34,5 @@ data:
         lastTransitionTime: "1970-01-01T00:00:00Z"
         reason: "Accepted"
         message: ""
+      ingresses: []
       observedGeneration: 2
\ No newline at end of file
diff --git a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/colliding-secret-interpolation/out-Service-foo-cluster-ip.yaml b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/colliding-secret-interpolation/out-Service-foo-cluster-ip.yaml
index b4a24de9..b6a86a9c 100644
--- a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/colliding-secret-interpolation/out-Service-foo-cluster-ip.yaml
+++ b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/colliding-secret-interpolation/out-Service-foo-cluster-ip.yaml
@@ -20,6 +20,12 @@ metadata:
     - apiVersion: "kroxylicious.io/v1alpha1"
       kind: "KafkaProxy"
       name: "example"
+    - apiVersion: "kroxylicious.io/v1alpha1"
+      kind: "VirtualKafkaCluster"
+      name: "foo"
+    - apiVersion: "kroxylicious.io/v1alpha1"
+      kind: "KafkaProxyIngress"
+      name: "cluster-ip"
 spec:
   ports:
     - name: "foo-9292"
diff --git a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/filter-resource-not-found/out-ConfigMap-example-config-state.yaml b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/filter-resource-not-found/out-ConfigMap-example-config-state.yaml
index 80c118d7..a00de917 100644
--- a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/filter-resource-not-found/out-ConfigMap-example-config-state.yaml
+++ b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/filter-resource-not-found/out-ConfigMap-example-config-state.yaml
@@ -35,6 +35,7 @@ data:
         reason: "Invalid"
         message: "Resource kafkaprotocolfilter.filter.kroxylicious.io/missing in namespace\
           \ 'proxy-ns' was not found."
+      ingresses: []
       observedGeneration: 4
   cluster-foo: |-
     ---
@@ -49,4 +50,5 @@ data:
         lastTransitionTime: "1970-01-01T00:00:00Z"
         reason: "Invalid"
         message: "Ingress(es) [cluster-ip-foo] of cluster conflicts with another ingress"
+      ingresses: []
       observedGeneration: 1
diff --git a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/minimal/out-ConfigMap-minimal-config-state.yaml b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/minimal/out-ConfigMap-minimal-config-state.yaml
index b4c9de39..6c5c487b 100644
--- a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/minimal/out-ConfigMap-minimal-config-state.yaml
+++ b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/minimal/out-ConfigMap-minimal-config-state.yaml
@@ -34,4 +34,5 @@ data:
         lastTransitionTime: "1970-01-01T00:00:00Z"
         reason: "Accepted"
         message: ""
+      ingresses: []
       observedGeneration: 4
diff --git a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/minimal/out-Service-one-cluster-ip.yaml b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/minimal/out-Service-one-cluster-ip.yaml
index ca97c6d9..f6f71092 100644
--- a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/minimal/out-Service-one-cluster-ip.yaml
+++ b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/minimal/out-Service-one-cluster-ip.yaml
@@ -20,6 +20,12 @@ metadata:
     - apiVersion: "kroxylicious.io/v1alpha1"
       kind: "KafkaProxy"
       name: "minimal"
+    - apiVersion: "kroxylicious.io/v1alpha1"
+      kind: "VirtualKafkaCluster"
+      name: "one"
+    - apiVersion: "kroxylicious.io/v1alpha1"
+      kind: "KafkaProxyIngress"
+      name: "cluster-ip"
 spec:
   ports:
     - name: "one-9292"
diff --git a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-clusters-one-with-unresolved-ingress/out-ConfigMap-twocluster-config-state.yaml b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-clusters-one-with-unresolved-ingress/out-ConfigMap-twocluster-config-state.yaml
index 965ad2dc..159f1326 100644
--- a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-clusters-one-with-unresolved-ingress/out-ConfigMap-twocluster-config-state.yaml
+++ b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-clusters-one-with-unresolved-ingress/out-ConfigMap-twocluster-config-state.yaml
@@ -35,6 +35,7 @@ data:
         reason: "Invalid"
         message: "Resource kafkaproxyingress.kroxylicious.io/MISSING in namespace 'proxy-ns'\
           \ was not found."
+      ingresses: []
       observedGeneration: 4
   cluster-bar: |-
     ---
@@ -49,4 +50,5 @@ data:
         lastTransitionTime: "1970-01-01T00:00:00Z"
         reason: "Accepted"
         message: ""
+      ingresses: []
       observedGeneration: 3
diff --git a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-clusters-one-with-unresolved-ingress/out-Service-bar-cluster-ip-bar.yaml b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-clusters-one-with-unresolved-ingress/out-Service-bar-cluster-ip-bar.yaml
index 723db6a8..300323c8 100644
--- a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-clusters-one-with-unresolved-ingress/out-Service-bar-cluster-ip-bar.yaml
+++ b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-clusters-one-with-unresolved-ingress/out-Service-bar-cluster-ip-bar.yaml
@@ -20,6 +20,12 @@ metadata:
     - apiVersion: "kroxylicious.io/v1alpha1"
       kind: "KafkaProxy"
       name: "twocluster"
+    - apiVersion: "kroxylicious.io/v1alpha1"
+      kind: "VirtualKafkaCluster"
+      name: "bar"
+    - apiVersion: "kroxylicious.io/v1alpha1"
+      kind: "KafkaProxyIngress"
+      name: "cluster-ip-bar"
 spec:
   ports:
     - name: "bar-9292"
diff --git a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-clusters-with-clusterip-ingress/out-ConfigMap-twocluster-config-state.yaml b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-clusters-with-clusterip-ingress/out-ConfigMap-twocluster-config-state.yaml
index fe9514d4..2315cd5f 100644
--- a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-clusters-with-clusterip-ingress/out-ConfigMap-twocluster-config-state.yaml
+++ b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-clusters-with-clusterip-ingress/out-ConfigMap-twocluster-config-state.yaml
@@ -34,6 +34,7 @@ data:
         lastTransitionTime: "1970-01-01T00:00:00Z"
         reason: "Accepted"
         message: ""
+      ingresses: []
       observedGeneration: 6
   cluster-foo: |-
     ---
@@ -48,5 +49,6 @@ data:
         lastTransitionTime: "1970-01-01T00:00:00Z"
         reason: "Invalid"
         message: "Ingress(es) [cluster-ip-foo] of cluster conflicts with another ingress"
+      ingresses: []
       observedGeneration: 7
 
diff --git a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-clusters-with-clusterip-ingress/out-Service-bar-cluster-ip-bar.yaml b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-clusters-with-clusterip-ingress/out-Service-bar-cluster-ip-bar.yaml
index 723db6a8..300323c8 100644
--- a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-clusters-with-clusterip-ingress/out-Service-bar-cluster-ip-bar.yaml
+++ b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-clusters-with-clusterip-ingress/out-Service-bar-cluster-ip-bar.yaml
@@ -20,6 +20,12 @@ metadata:
     - apiVersion: "kroxylicious.io/v1alpha1"
       kind: "KafkaProxy"
       name: "twocluster"
+    - apiVersion: "kroxylicious.io/v1alpha1"
+      kind: "VirtualKafkaCluster"
+      name: "bar"
+    - apiVersion: "kroxylicious.io/v1alpha1"
+      kind: "KafkaProxyIngress"
+      name: "cluster-ip-bar"
 spec:
   ports:
     - name: "bar-9292"
diff --git a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-clusters-with-colliding-ingress-and-unresolved-filter/out-ConfigMap-twocluster-config-state.yaml b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-clusters-with-colliding-ingress-and-unresolved-filter/out-ConfigMap-twocluster-config-state.yaml
index 447a3b60..bd07b955 100644
--- a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-clusters-with-colliding-ingress-and-unresolved-filter/out-ConfigMap-twocluster-config-state.yaml
+++ b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-clusters-with-colliding-ingress-and-unresolved-filter/out-ConfigMap-twocluster-config-state.yaml
@@ -35,6 +35,7 @@ data:
         reason: "Invalid"
         message: "Resource kafkaservice.kroxylicious.io/barref in namespace 'proxy-ns'\
           \ was not found."
+      ingresses: []
       observedGeneration: 1
   cluster-foo: |-
     ---
@@ -49,4 +50,5 @@ data:
         lastTransitionTime: "1970-01-01T00:00:00Z"
         reason: "Invalid"
         message: "Ingress(es) [cluster-ip-foo] of cluster conflicts with another ingress"
+      ingresses: []
       observedGeneration: 2
\ No newline at end of file
diff --git a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-filters/out-ConfigMap-example-config-state.yaml b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-filters/out-ConfigMap-example-config-state.yaml
index 71df44db..a673e844 100644
--- a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-filters/out-ConfigMap-example-config-state.yaml
+++ b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-filters/out-ConfigMap-example-config-state.yaml
@@ -34,4 +34,5 @@ data:
         lastTransitionTime: "1970-01-01T00:00:00Z"
         reason: "Accepted"
         message: ""
+      ingresses: []
       observedGeneration: 12
diff --git a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-filters/out-Service-foo-cluster-ip.yaml b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-filters/out-Service-foo-cluster-ip.yaml
index 0857ad06..235fe7ed 100644
--- a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-filters/out-Service-foo-cluster-ip.yaml
+++ b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/two-filters/out-Service-foo-cluster-ip.yaml
@@ -20,6 +20,12 @@ metadata:
     - apiVersion: "kroxylicious.io/v1alpha1"
       kind: "KafkaProxy"
       name: "example"
+    - apiVersion: "kroxylicious.io/v1alpha1"
+      kind: "VirtualKafkaCluster"
+      name: "foo"
+    - apiVersion: "kroxylicious.io/v1alpha1"
+      kind: "KafkaProxyIngress"
+      name: "cluster-ip"
 spec:
   ports:
     - name: "foo-9292"
diff --git a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/use-pod-template-spec/out-ConfigMap-use-pod-template-spec-config-state.yaml b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/use-pod-template-spec/out-ConfigMap-use-pod-template-spec-config-state.yaml
index 4e06ecb0..b47ff4b4 100644
--- a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/use-pod-template-spec/out-ConfigMap-use-pod-template-spec-config-state.yaml
+++ b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/use-pod-template-spec/out-ConfigMap-use-pod-template-spec-config-state.yaml
@@ -34,4 +34,5 @@ data:
         lastTransitionTime: "1970-01-01T00:00:00Z"
         reason: "Accepted"
         message: ""
+      ingresses: []
       observedGeneration: 5
diff --git a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/use-pod-template-spec/out-Service-one-cluster-ip.yaml b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/use-pod-template-spec/out-Service-one-cluster-ip.yaml
index e6acf09a..47a886eb 100644
--- a/kroxylicious-operator/src/test/resources/DerivedResourcesTest/use-pod-template-spec/out-Service-one-cluster-ip.yaml
+++ b/kroxylicious-operator/src/test/resources/DerivedResourcesTest/use-pod-template-spec/out-Service-one-cluster-ip.yaml
@@ -20,6 +20,12 @@ metadata:
     - apiVersion: "kroxylicious.io/v1alpha1"
       kind: "KafkaProxy"
       name: "use-pod-template-spec"
+    - apiVersion: "kroxylicious.io/v1alpha1"
+      kind: "VirtualKafkaCluster"
+      name: "one"
+    - apiVersion: "kroxylicious.io/v1alpha1"
+      kind: "KafkaProxyIngress"
+      name: "cluster-ip"
 spec:
   ports:
     - name: "one-9292"