Repository: kroxylicious/kroxylicious
Pull request #253 (2023-04-04 14:57:24)

Use sundr builder annotation to build the config for the integration tests.
### Type of change

_Select the type of your PR_

- Refactoring

### Description

Use sundr builder annotation to build the config for the integration tests.


### Additional Context

The hand rolled builder methods in KafkaConfig were getting in my way.



k-wall: It'd been nicer to eliminate the parallel class hierarchy in the integration test test and have Kroxy's configuration fluently buildable.  I tried that approach but couldn't get either sundr or lombok to play nicely with the existing config classes.

k-wall: I'd quite like to merge this PR on the understanding we/I come back to it soon and get rid of the second config model.  
robobario: Cool library. It's hard to reconcile this nice typesafe builder flow with our jackson `FilterConfig` which is part of the filter api for authors.

Maybe we could make this builder-style config the one that drives Kroxy but map the jackson-config to it. So for kroxy we would load `yaml -> jackson deserialized config -> builder config` (having to maintain the adapter code :grimacing:)  and for integration tests we would directly build the `builder config`.

Patch:
diff --git a/etc/checkstyle-suppressions.xml b/etc/checkstyle-suppressions.xml
index b02f04de..1e4d61c2 100644
--- a/etc/checkstyle-suppressions.xml
+++ b/etc/checkstyle-suppressions.xml
@@ -11,4 +11,7 @@
         "https://checkstyle.org/dtds/suppressions_1_2.dtd">
 
 <suppressions>
+    <!-- Generated classes which we do not edit -->
+    <suppress checks=".*"
+              files="io[/\\]kroxylicious[/\\]proxy[/\\].*(Builder|Editable|Fluent|Nested|Visitor|Visitable).*\.java"/>
 </suppressions>
diff --git a/pom.xml b/pom.xml
index 73e19552..30df4975 100644
--- a/pom.xml
+++ b/pom.xml
@@ -50,6 +50,7 @@
         <micrometer.version>1.10.5</micrometer.version>
         <kroxy.extension.version>0.1</kroxy.extension.version>
         <zjsonpatch.version>0.4.14</zjsonpatch.version>
+        <sundr-builder-annotations.version>0.94.0</sundr-builder-annotations.version>
     </properties>
 
     <name>Kroxylicious Parent</name>
@@ -206,6 +207,12 @@
                 <version>${zjsonpatch.version}</version>
                 <scope>test</scope>
             </dependency>
+            <dependency>
+                <groupId>io.sundr</groupId>
+                <artifactId>builder-annotations</artifactId>
+                <version>${sundr-builder-annotations.version}</version>
+                <scope>provided</scope>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 


Test patch:
diff --git a/integrationtests/pom.xml b/integrationtests/pom.xml
index a0c738cb..b5871409 100644
--- a/integrationtests/pom.xml
+++ b/integrationtests/pom.xml
@@ -66,6 +66,11 @@
             <artifactId>assertj-core</artifactId>
             <scope>compile</scope>
         </dependency>
+        <!-- https://mvnrepository.com/artifact/io.sundr/builder-annotations -->
+        <dependency>
+            <groupId>io.sundr</groupId>
+            <artifactId>builder-annotations</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/integrationtests/src/test/java/io/kroxylicious/proxy/AdminHttp.java b/integrationtests/src/test/java/io/kroxylicious/proxy/AdminHttp.java
new file mode 100644
index 00000000..6a8d4017
--- /dev/null
+++ b/integrationtests/src/test/java/io/kroxylicious/proxy/AdminHttp.java
@@ -0,0 +1,13 @@
+/*
+ * Copyright Kroxylicious Authors.
+ *
+ * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+package io.kroxylicious.proxy;
+
+import io.sundr.builder.annotations.Buildable;
+
+@Buildable(editableEnabled = false)
+public record AdminHttp(Endpoints endpoints) {
+}
diff --git a/integrationtests/src/test/java/io/kroxylicious/proxy/Cluster.java b/integrationtests/src/test/java/io/kroxylicious/proxy/Cluster.java
new file mode 100644
index 00000000..3003e4b7
--- /dev/null
+++ b/integrationtests/src/test/java/io/kroxylicious/proxy/Cluster.java
@@ -0,0 +1,15 @@
+/*
+ * Copyright Kroxylicious Authors.
+ *
+ * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+package io.kroxylicious.proxy;
+
+import com.fasterxml.jackson.annotation.JsonGetter;
+
+import io.sundr.builder.annotations.Buildable;
+
+@Buildable(editableEnabled = false)
+public record Cluster(@JsonGetter("bootstrap_servers") String bootstrapServers) {
+}
diff --git a/integrationtests/src/test/java/io/kroxylicious/proxy/Endpoints.java b/integrationtests/src/test/java/io/kroxylicious/proxy/Endpoints.java
new file mode 100644
index 00000000..698c4db1
--- /dev/null
+++ b/integrationtests/src/test/java/io/kroxylicious/proxy/Endpoints.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright Kroxylicious Authors.
+ *
+ * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+package io.kroxylicious.proxy;
+
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonGetter;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import io.sundr.builder.annotations.Buildable;
+
+import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;
+
+@Buildable(editableEnabled = false)
+public record Endpoints(@JsonGetter("prometheus") @JsonInclude(NON_NULL) Map<String, String> prometheusEndpointConfig) {
+}
diff --git a/integrationtests/src/test/java/io/kroxylicious/proxy/Filter.java b/integrationtests/src/test/java/io/kroxylicious/proxy/Filter.java
new file mode 100644
index 00000000..b5549c8c
--- /dev/null
+++ b/integrationtests/src/test/java/io/kroxylicious/proxy/Filter.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright Kroxylicious Authors.
+ *
+ * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+package io.kroxylicious.proxy;
+
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import io.sundr.builder.annotations.Buildable;
+
+import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY;
+
+@Buildable(editableEnabled = false)
+public record Filter(String type, @JsonInclude(NON_EMPTY) Map<String, Object> config) {
+}
diff --git a/integrationtests/src/test/java/io/kroxylicious/proxy/KroxyConfig.java b/integrationtests/src/test/java/io/kroxylicious/proxy/KroxyConfig.java
new file mode 100644
index 00000000..397ab005
--- /dev/null
+++ b/integrationtests/src/test/java/io/kroxylicious/proxy/KroxyConfig.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright Kroxylicious Authors.
+ *
+ * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package io.kroxylicious.proxy;
+
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
+
+import io.sundr.builder.annotations.Buildable;
+
+import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;
+
+@Buildable(editableEnabled = false)
+
+public class KroxyConfig {
+
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER));
+
+    private final Proxy proxy;
+    private final Map<String, Cluster> clusters;
+    private final List<Filter> filters;
+
+    @JsonInclude(NON_NULL)
+    private final AdminHttp adminHttp;
+
+    @JsonInclude(NON_NULL)
+    private final List<MicrometerConfig> micrometer;
+
+    public static KroxyConfigBuilder builder() {
+        return new KroxyConfigBuilder();
+    }
+
+    public KroxyConfig(Proxy proxy, Map<String, Cluster> clusters, List<Filter> filters, AdminHttp adminHttp, List<MicrometerConfig> micrometer) {
+        this.proxy = proxy;
+        this.clusters = clusters;
+        this.filters = filters;
+        this.adminHttp = adminHttp;
+        this.micrometer = micrometer;
+    }
+
+    public String toYaml() {
+        try {
+            return OBJECT_MAPPER.writeValueAsString(this);
+        }
+        catch (JsonProcessingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public Proxy getProxy() {
+        return proxy;
+    }
+
+    public AdminHttp getAdminHttp() {
+        return adminHttp;
+    }
+
+    public Map<String, Cluster> getClusters() {
+        return clusters;
+    }
+
+    public List<Filter> getFilters() {
+        return filters;
+    }
+
+    public List<MicrometerConfig> getMicrometer() {
+        return micrometer;
+    }
+}
diff --git a/integrationtests/src/test/java/io/kroxylicious/proxy/KroxyConfigBuilder.java b/integrationtests/src/test/java/io/kroxylicious/proxy/KroxyConfigBuilder.java
deleted file mode 100644
index 8d35a52d..00000000
--- a/integrationtests/src/test/java/io/kroxylicious/proxy/KroxyConfigBuilder.java
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright Kroxylicious Authors.
- *
- * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
- */
-package io.kroxylicious.proxy;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-import com.fasterxml.jackson.annotation.JsonGetter;
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
-import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
-
-import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY;
-import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;
-
-public class KroxyConfigBuilder {
-
-    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(new YAMLFactory().disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER));
-
-    public record Proxy(String address,
-                        @JsonInclude(NON_NULL) String keyStoreFile,
-                        @JsonInclude(NON_NULL) String keyPassword) {
-    }
-
-    public record Cluster(@JsonGetter("bootstrap_servers") String bootstrapServers) {
-    }
-
-    public record Filter(String type, @JsonInclude(NON_EMPTY) Map<String, Object> config) {
-    }
-
-    public record MicrometerConfig(String type, @JsonInclude(NON_EMPTY) Map<String, Object> config) {
-    }
-
-    public record AdminHttp(Endpoints endpoints) {
-    }
-
-    public record Endpoints(@JsonGetter("prometheus") @JsonInclude(NON_NULL) Map<String, String> prometheusEndpointConfig) {
-    }
-
-    private Proxy proxy;
-    private final Map<String, Cluster> clusters = new LinkedHashMap<>();
-    private final List<Filter> filters = new ArrayList<>();
-
-    @JsonInclude(NON_NULL)
-    private AdminHttp adminHttp = null;
-
-    @JsonInclude(NON_NULL)
-    private List<MicrometerConfig> micrometer = null;
-
-    public KroxyConfigBuilder(String proxyAddress) {
-        proxy = new Proxy(proxyAddress, null, null);
-    }
-
-    public KroxyConfigBuilder withDefaultCluster(String bootstrapServers) {
-        return withCluster("demo", bootstrapServers);
-    }
-
-    public KroxyConfigBuilder withMicrometerBinder(String binder) {
-        MicrometerConfig config = getMicrometerConfigForType("StandardBinders");
-        @SuppressWarnings("unchecked")
-        List<String> binderNames = (List<String>) config.config.computeIfAbsent("binderNames", s -> new ArrayList<String>());
-        binderNames.add(binder);
-        return this;
-    }
-
-    public KroxyConfigBuilder withMicrometerCommonTag(String key, String value) {
-        MicrometerConfig config = getMicrometerConfigForType("CommonTags");
-        @SuppressWarnings("unchecked")
-        Map<String, String> commonTags = (Map<String, String>) config.config.computeIfAbsent("commonTags", s -> new HashMap<String, String>());
-        commonTags.put(key, value);
-        return this;
-    }
-
-    private MicrometerConfig getMicrometerConfigForType(String type) {
-        Optional<MicrometerConfig> standardBinders = getOrCreateMicrometer().stream().filter(micrometerConfig -> micrometerConfig.type.equals(type)).findFirst();
-        MicrometerConfig config;
-        if (standardBinders.isPresent()) {
-            config = standardBinders.get();
-        }
-        else {
-            config = new MicrometerConfig(type, new HashMap<>());
-            micrometer.add(config);
-        }
-        return config;
-    }
-
-    private List<MicrometerConfig> getOrCreateMicrometer() {
-        if (micrometer == null) {
-            micrometer = new ArrayList<>();
-        }
-        return micrometer;
-    }
-
-    public KroxyConfigBuilder withCluster(String clusterName, String bootstrapServers) {
-        clusters.put(clusterName, new Cluster(bootstrapServers));
-        return this;
-    }
-
-    public KroxyConfigBuilder withKeyStoreConfig(String keystoreFile, String keyPassword) {
-        String address = proxy == null ? null : proxy.address;
-        proxy = new Proxy(address, keystoreFile, keyPassword);
-        return this;
-    }
-
-    public KroxyConfigBuilder withPrometheusEndpoint() {
-        adminHttp = new AdminHttp(new Endpoints(Map.of()));
-        return this;
-    }
-
-    public KroxyConfigBuilder addFilter(String type) {
-        filters.add(new Filter(type, null));
-        return this;
-    }
-
-    public KroxyConfigBuilder addFilter(String type, String configKey, Object configValue) {
-        filters.add(new Filter(type, Map.of(configKey, configValue)));
-        return this;
-    }
-
-    public String build() {
-        try {
-            return OBJECT_MAPPER.writeValueAsString(this);
-        }
-        catch (JsonProcessingException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    public Proxy getProxy() {
-        return proxy;
-    }
-
-    public AdminHttp getAdminHttp() {
-        return adminHttp;
-    }
-
-    public Map<String, Cluster> getClusters() {
-        return clusters;
-    }
-
-    public List<Filter> getFilters() {
-        return filters;
-    }
-
-    public List<MicrometerConfig> getMicrometer() {
-        return micrometer;
-    }
-}
diff --git a/integrationtests/src/test/java/io/kroxylicious/proxy/KroxyConfigBuilderTest.java b/integrationtests/src/test/java/io/kroxylicious/proxy/KroxyConfigTest.java
similarity index 78%
rename from integrationtests/src/test/java/io/kroxylicious/proxy/KroxyConfigBuilderTest.java
rename to integrationtests/src/test/java/io/kroxylicious/proxy/KroxyConfigTest.java
index 4a66cd65..3f29d260 100644
--- a/integrationtests/src/test/java/io/kroxylicious/proxy/KroxyConfigBuilderTest.java
+++ b/integrationtests/src/test/java/io/kroxylicious/proxy/KroxyConfigTest.java
@@ -5,7 +5,7 @@
  */
 package io.kroxylicious.proxy;
 
-import java.io.IOException;
+import java.util.Map;
 
 import org.junit.jupiter.api.Test;
 
@@ -15,23 +15,24 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
 
+import static io.kroxylicious.proxy.KroxyConfig.builder;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
-public class KroxyConfigBuilderTest {
+public class KroxyConfigTest {
     private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(new YAMLFactory());
 
     @Test
-    public void testBareConfig() throws IOException {
-        ObjectNode deserializedConfig = serializeAndDeserialize(new KroxyConfigBuilder("localhost:9192"));
+    public void testBareConfig() throws Exception {
+        ObjectNode deserializedConfig = serializeAndDeserialize(builder().withNewProxy().withAddress("localhost:9192").endProxy().build());
         ObjectNode proxyObj = assertObjectField(deserializedConfig, "proxy");
         assertTextField(proxyObj, "address", "localhost:9192");
     }
 
     @Test
-    public void testSslConfig() throws IOException {
-        ObjectNode deserializedConfig = serializeAndDeserialize(new KroxyConfigBuilder("localhost:9192")
-                .withKeyStoreConfig("file", "pass"));
+    public void testSslConfig() throws Exception {
+        ObjectNode deserializedConfig = serializeAndDeserialize(
+                builder().withNewProxy().withAddress("localhost:9192").withKeyPassword("pass").withKeyStoreFile("file").endProxy().build());
         ObjectNode proxyObj = assertObjectField(deserializedConfig, "proxy");
         assertTextField(proxyObj, "address", "localhost:9192");
         assertTextField(proxyObj, "keyStoreFile", "file");
@@ -39,27 +40,25 @@ public class KroxyConfigBuilderTest {
     }
 
     @Test
-    public void testClusterConfig() throws IOException {
-        ObjectNode deserializedConfig = serializeAndDeserialize(new KroxyConfigBuilder("localhost:9192")
-                .withDefaultCluster("localhost:9092"));
+    public void testClusterConfig() throws Exception {
+        ObjectNode deserializedConfig = serializeAndDeserialize(
+                builder().addToClusters("demo", new ClusterBuilder().withBootstrapServers("localhost:9092").build()).build());
         ObjectNode clusterObj = assertObjectField(deserializedConfig, "clusters");
         ObjectNode demoObj = assertObjectField(clusterObj, "demo");
         assertTextField(demoObj, "bootstrap_servers", "localhost:9092");
     }
 
     @Test
-    public void testTypeOnlyFilter() throws IOException {
-        ObjectNode deserializedConfig = serializeAndDeserialize(new KroxyConfigBuilder("localhost:9192")
-                .addFilter("FilterType"));
+    public void testTypeOnlyFilter() throws Exception {
+        ObjectNode deserializedConfig = serializeAndDeserialize(builder().addNewFilter().withType("FilterType").endFilter().build());
         ArrayNode filters = assertArrayField(deserializedConfig, "filters", 1);
         ObjectNode filterObj = assertOnlyElementIsObject(filters);
         assertTextField(filterObj, "type", "FilterType");
     }
 
     @Test
-    public void testFilterWithSingleParam() throws IOException {
-        ObjectNode deserializedConfig = serializeAndDeserialize(new KroxyConfigBuilder("localhost:9192")
-                .addFilter("FilterType", "a", "b"));
+    public void testFilterWithSingleParam() throws Exception {
+        ObjectNode deserializedConfig = serializeAndDeserialize(builder().addNewFilter().withType("FilterType").withConfig(Map.of("a", "b")).endFilter().build());
         ArrayNode filters = assertArrayField(deserializedConfig, "filters", 1);
         ObjectNode filterObj = assertOnlyElementIsObject(filters);
         assertTextField(filterObj, "type", "FilterType");
@@ -68,17 +67,17 @@ public class KroxyConfigBuilderTest {
     }
 
     @Test
-    public void testPrometheusEndpointConfig() throws IOException {
-        ObjectNode deserializedConfig = serializeAndDeserialize(new KroxyConfigBuilder("localhost:9192")
-                .withPrometheusEndpoint());
+    public void testPrometheusEndpointConfig() throws Exception {
+        ObjectNode deserializedConfig = serializeAndDeserialize(
+                builder().withNewAdminHttp().withNewEndpoints().withPrometheusEndpointConfig(Map.of()).endEndpoints().endAdminHttp().build());
         ObjectNode adminHttp = assertObjectField(deserializedConfig, "adminHttp");
         ObjectNode endpoints = assertObjectField(adminHttp, "endpoints");
         ObjectNode prometheus = assertObjectField(endpoints, "prometheus");
         assertTrue(prometheus.isEmpty(), "expect prometheus endpoint to have an empty object serialized");
     }
 
-    private static ObjectNode serializeAndDeserialize(KroxyConfigBuilder builder) throws IOException {
-        String config = builder.build();
+    private static ObjectNode serializeAndDeserialize(KroxyConfig builder) throws Exception {
+        String config = builder.toYaml();
         return OBJECT_MAPPER.reader().readValue(config, ObjectNode.class);
     }
 
diff --git a/integrationtests/src/test/java/io/kroxylicious/proxy/KrpcFilterIT.java b/integrationtests/src/test/java/io/kroxylicious/proxy/KrpcFilterIT.java
index c36c1185..401c1a8d 100644
--- a/integrationtests/src/test/java/io/kroxylicious/proxy/KrpcFilterIT.java
+++ b/integrationtests/src/test/java/io/kroxylicious/proxy/KrpcFilterIT.java
@@ -114,7 +114,7 @@ public class KrpcFilterIT {
 
         admin.createTopics(List.of(new NewTopic(TOPIC_1, 1, (short) 1))).all().get();
 
-        var config = baseConfigBuilder(proxyAddress, cluster.getBootstrapServers()).build();
+        var config = baseConfigBuilder(proxyAddress, cluster.getBootstrapServers()).build().toYaml();
 
         try (var proxy = startProxy(config)) {
             try (var producer = new KafkaProducer<String, String>(Map.of(
@@ -150,8 +150,9 @@ public class KrpcFilterIT {
                 new NewTopic(TOPIC_2, 1, (short) 1))).all().get();
 
         var config = baseConfigBuilder(proxyAddress, cluster.getBootstrapServers())
-                .addFilter("ProduceRequestTransformation", "transformation", TestEncoder.class.getName())
-                .build();
+                .addNewFilter().withType("ProduceRequestTransformation").withConfig(Map.of("transformation", TestEncoder.class.getName())).endFilter()
+                .build()
+                .toYaml();
 
         try (var proxy = startProxy(config)) {
             try (var producer = new KafkaProducer<String, String>(Map.of(
@@ -195,8 +196,9 @@ public class KrpcFilterIT {
                 new NewTopic(TOPIC_2, 1, (short) 1))).all().get();
 
         var config = baseConfigBuilder(proxyAddress, cluster.getBootstrapServers())
-                .addFilter("FetchResponseTransformation", "transformation", TestDecoder.class.getName())
-                .build();
+                .addNewFilter().withType("FetchResponseTransformation").withConfig(Map.of("transformation", TestDecoder.class.getName())).endFilter()
+                .build()
+                .toYaml();
 
         try (var proxy = startProxy(config)) {
             try (var producer = new KafkaProducer<String, byte[]>(Map.of(
@@ -245,8 +247,12 @@ public class KrpcFilterIT {
                 clientTrustStore.toAbsolutePath().toString());
 
         var config = baseConfigBuilder(proxyAddress, cluster.getBootstrapServers())
-                .withKeyStoreConfig(brokerCertificateGenerator.getKeyStoreLocation(), brokerCertificateGenerator.getPassword())
-                .build();
+                .editProxy()
+                .withKeyPassword(brokerCertificateGenerator.getPassword())
+                .withKeyStoreFile(brokerCertificateGenerator.getKeyStoreLocation())
+                .endProxy()
+                .build()
+                .toYaml();
 
         try (var proxy = startProxy(config)) {
             try (var admin = Admin.create(Map.of(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, proxyAddress,
@@ -265,10 +271,10 @@ public class KrpcFilterIT {
     }
 
     private static KroxyConfigBuilder baseConfigBuilder(String proxyAddress, String bootstrapServers) {
-        return new KroxyConfigBuilder(proxyAddress)
-                .withDefaultCluster(bootstrapServers)
-                .addFilter("ApiVersions")
-                .addFilter("BrokerAddress");
+        return KroxyConfig.builder().withNewProxy().withAddress(proxyAddress).endProxy()
+                .addToClusters("demo", new ClusterBuilder().withBootstrapServers(bootstrapServers).build())
+                .addNewFilter().withType("ApiVersions").endFilter()
+                .addNewFilter().withType("BrokerAddress").endFilter();
     }
 
 }
diff --git a/integrationtests/src/test/java/io/kroxylicious/proxy/MetricsIT.java b/integrationtests/src/test/java/io/kroxylicious/proxy/MetricsIT.java
index a3cc0f93..095e135b 100644
--- a/integrationtests/src/test/java/io/kroxylicious/proxy/MetricsIT.java
+++ b/integrationtests/src/test/java/io/kroxylicious/proxy/MetricsIT.java
@@ -10,6 +10,8 @@ import java.net.http.HttpClient;
 import java.net.http.HttpRequest;
 import java.net.http.HttpResponse;
 import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
 import java.util.Random;
 import java.util.regex.Pattern;
 
@@ -41,7 +43,13 @@ public class MetricsIT {
     @Test
     public void shouldOfferPrometheusMetricsScrapeEndpoint(KafkaCluster cluster) throws Exception {
         String config = baseConfigBuilder(PROXY_ADDRESS, cluster.getBootstrapServers())
-                .withPrometheusEndpoint().build();
+                .withNewAdminHttp()
+                .withNewEndpoints()
+                .withPrometheusEndpointConfig(Map.of())
+                .endEndpoints()
+                .endAdminHttp()
+                .build()
+                .toYaml();
 
         try (var proxy = startProxy(config)) {
             String counter_name = "test_metric_" + Math.abs(new Random().nextLong()) + "_total";
@@ -58,8 +66,17 @@ public class MetricsIT {
     @Test
     public void shouldOfferPrometheusMetricsWithNamedBinder(KafkaCluster cluster) throws Exception {
         String config = baseConfigBuilder(PROXY_ADDRESS, cluster.getBootstrapServers())
-                .withMicrometerBinder("JvmGcMetrics")
-                .withPrometheusEndpoint().build();
+                .addToMicrometer(new MicrometerConfigBuilder()
+                        .withType("StandardBinders")
+                        .withConfig(Map.of("binderNames", List.of("JvmGcMetrics")))
+                        .build())
+                .withNewAdminHttp()
+                .withNewEndpoints()
+                .withPrometheusEndpointConfig(Map.of())
+                .endEndpoints()
+                .endAdminHttp()
+                .build()
+                .toYaml();
 
         try (var proxy = startProxy(config)) {
             HttpRequest request = HttpRequest.newBuilder(URI.create("http://localhost:9193/metrics")).GET().build();
@@ -71,8 +88,14 @@ public class MetricsIT {
     @Test
     public void shouldOfferPrometheusMetricsWithCommonTags(KafkaCluster cluster) throws Exception {
         String config = baseConfigBuilder(PROXY_ADDRESS, cluster.getBootstrapServers())
-                .withMicrometerCommonTag("a", "b")
-                .withPrometheusEndpoint().build();
+                .addNewMicrometer().withType("CommonTags").withConfig(Map.of("commonTags", Map.of("a", "b"))).endMicrometer()
+                .withNewAdminHttp()
+                .withNewEndpoints()
+                .withPrometheusEndpointConfig(Map.of())
+                .endEndpoints()
+                .endAdminHttp()
+                .build()
+                .toYaml();
 
         try (var proxy = startProxy(config)) {
             String counter_name = "test_metric_" + Math.abs(new Random().nextLong()) + "_total";
@@ -100,9 +123,7 @@ public class MetricsIT {
     }
 
     private static KroxyConfigBuilder baseConfigBuilder(String proxyAddress, String bootstrapServers) {
-        return new KroxyConfigBuilder(proxyAddress)
-                .withDefaultCluster(bootstrapServers)
-                .addFilter("ApiVersions")
-                .addFilter("BrokerAddress");
+        return KroxyConfig.builder().withNewProxy().withAddress(proxyAddress).endProxy()
+                .addToClusters("demo", new ClusterBuilder().withBootstrapServers(bootstrapServers).build());
     }
 }
diff --git a/integrationtests/src/test/java/io/kroxylicious/proxy/MicrometerConfig.java b/integrationtests/src/test/java/io/kroxylicious/proxy/MicrometerConfig.java
new file mode 100644
index 00000000..5eb1cb9c
--- /dev/null
+++ b/integrationtests/src/test/java/io/kroxylicious/proxy/MicrometerConfig.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright Kroxylicious Authors.
+ *
+ * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+package io.kroxylicious.proxy;
+
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import io.sundr.builder.annotations.Buildable;
+
+import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY;
+
+@Buildable(editableEnabled = false)
+public record MicrometerConfig(String type, @JsonInclude(NON_EMPTY) Map<String, Object> config) {
+}
diff --git a/integrationtests/src/test/java/io/kroxylicious/proxy/Proxy.java b/integrationtests/src/test/java/io/kroxylicious/proxy/Proxy.java
new file mode 100644
index 00000000..f0b4cbe1
--- /dev/null
+++ b/integrationtests/src/test/java/io/kroxylicious/proxy/Proxy.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright Kroxylicious Authors.
+ *
+ * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+package io.kroxylicious.proxy;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import io.sundr.builder.annotations.Buildable;
+
+import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;
+
+@Buildable(editableEnabled = false)
+public record Proxy(String address,
+                    @JsonInclude(NON_NULL) String keyStoreFile,
+                    @JsonInclude(NON_NULL) String keyPassword) {
+}
diff --git a/integrationtests/src/test/java/io/kroxylicious/proxy/multitenant/MultiTenantIT.java b/integrationtests/src/test/java/io/kroxylicious/proxy/multitenant/MultiTenantIT.java
index 1d94f983..906873c1 100644
--- a/integrationtests/src/test/java/io/kroxylicious/proxy/multitenant/MultiTenantIT.java
+++ b/integrationtests/src/test/java/io/kroxylicious/proxy/multitenant/MultiTenantIT.java
@@ -63,7 +63,8 @@ import org.junit.jupiter.params.provider.EnumSource;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import io.kroxylicious.proxy.KroxyConfigBuilder;
+import io.kroxylicious.proxy.ClusterBuilder;
+import io.kroxylicious.proxy.KroxyConfig;
 import io.kroxylicious.testing.kafka.api.KafkaCluster;
 import io.kroxylicious.testing.kafka.common.KeytoolCertificateGenerator;
 import io.kroxylicious.testing.kafka.junit5ext.KafkaClusterExtension;
@@ -455,13 +456,17 @@ public class MultiTenantIT {
     }
 
     private String getConfig(String proxyAddress, KafkaCluster cluster) {
-        return new KroxyConfigBuilder(proxyAddress)
-                .withDefaultCluster(cluster.getBootstrapServers())
-                .withKeyStoreConfig(certificateGenerator.getKeyStoreLocation(), certificateGenerator.getPassword())
-                .addFilter("ApiVersions")
-                .addFilter("BrokerAddress", "addressMapperClazz", "io.kroxylicious.proxy.internal.filter.SniAddressMapping")
-                .addFilter("MultiTenant")
-                .build();
+        return KroxyConfig.builder()
+                .withNewProxy()
+                .withAddress(proxyAddress)
+                .withKeyPassword(certificateGenerator.getPassword())
+                .withKeyStoreFile(certificateGenerator.getKeyStoreLocation())
+                .endProxy()
+                .addToClusters("demo", new ClusterBuilder().withBootstrapServers(cluster.getBootstrapServers()).build())
+                .addNewFilter().withType("ApiVersions").endFilter()
+                .addNewFilter().withType("BrokerAddress").withConfig(Map.of("addressMapperClazz", "io.kroxylicious.proxy.internal.filter.SniAddressMapping")).endFilter()
+                .addNewFilter().withType("MultiTenant").endFilter()
+                .build().toYaml();
     }
 
     @NotNull