T getResource(
.withName(name)
.get();
- if (resource == null) {
+ if (resource == null && !optional) {
throw new ReconciliationException("No such %s resource: %s/%s".formatted(resourceType.getSimpleName(), namespace, name));
}
diff --git a/operator/src/test/example-console.yaml b/operator/src/test/example-console.yaml
index 6a5844979..21748f796 100644
--- a/operator/src/test/example-console.yaml
+++ b/operator/src/test/example-console.yaml
@@ -2,11 +2,18 @@ apiVersion: console.streamshub.github.com/v1alpha1
kind: Console
metadata:
name: example
- namespace: streams-console
spec:
hostname: example-console.apps-crc.testing
kafkaClusters:
- - kafkaUserName: console-kafka-user1
+ - id: my-console
+ credentials:
+ kafkaUser:
+ name: console-kafka-user1
+ #namespace: same as kafkaCluster
listener: secure
name: console-kafka
namespace: streams-console
+ properties:
+ values:
+ - name: x-some-test-property
+ value: the-value
diff --git a/operator/src/test/java/com/github/streamshub/console/ConsoleReconcilerTest.java b/operator/src/test/java/com/github/streamshub/console/ConsoleReconcilerTest.java
index 1a64ea3ea..d4fac62ca 100644
--- a/operator/src/test/java/com/github/streamshub/console/ConsoleReconcilerTest.java
+++ b/operator/src/test/java/com/github/streamshub/console/ConsoleReconcilerTest.java
@@ -16,8 +16,11 @@
import com.github.streamshub.console.api.v1alpha1.Console;
import com.github.streamshub.console.api.v1alpha1.ConsoleBuilder;
import com.github.streamshub.console.config.ConsoleConfig;
+import com.github.streamshub.console.dependents.ConsoleResource;
import com.github.streamshub.console.dependents.ConsoleSecret;
+import io.fabric8.kubernetes.api.model.ConfigMap;
+import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import io.fabric8.kubernetes.api.model.NamespaceBuilder;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.fabric8.kubernetes.api.model.Secret;
@@ -64,17 +67,20 @@ void setUp() throws Exception {
var allConsoles = client.resources(Console.class).inAnyNamespace();
var allKafkas = client.resources(Kafka.class).inAnyNamespace();
var allKafkaUsers = client.resources(KafkaUser.class).inAnyNamespace();
- var allSecrets = client.resources(Secret.class).inAnyNamespace();
+ var allConfigMaps = client.resources(ConfigMap.class).inAnyNamespace().withLabels(ConsoleResource.MANAGEMENT_LABEL);
+ var allSecrets = client.resources(Secret.class).inAnyNamespace().withLabels(ConsoleResource.MANAGEMENT_LABEL);
allConsoles.delete();
allKafkas.delete();
allKafkaUsers.delete();
+ allConfigMaps.delete();
allSecrets.delete();
await().atMost(LIMIT).untilAsserted(() -> {
assertTrue(allConsoles.list().getItems().isEmpty());
assertTrue(allKafkas.list().getItems().isEmpty());
assertTrue(allKafkaUsers.list().getItems().isEmpty());
+ assertTrue(allConfigMaps.list().getItems().isEmpty());
assertTrue(allSecrets.list().getItems().isEmpty());
});
@@ -362,6 +368,7 @@ void testConsoleReconciliationWithMissingJaasConfigKey() {
.withNewMetadata()
.withName("ku1")
.withNamespace("ns1")
+ .addToLabels(ConsoleResource.MANAGEMENT_LABEL)
.endMetadata()
// no data map
.build();
@@ -431,6 +438,7 @@ void testConsoleReconciliationWithValidKafkaUser() {
.withNewMetadata()
.withName("ku1")
.withNamespace("ns1")
+ .addToLabels(ConsoleResource.MANAGEMENT_LABEL)
.endMetadata()
.addToData(SaslConfigs.SASL_JAAS_CONFIG, Base64.getEncoder().encodeToString("jaas-config-value".getBytes()))
.build();
@@ -480,6 +488,86 @@ void testConsoleReconciliationWithValidKafkaUser() {
});
}
+
+ @Test
+ void testConsoleReconciliationWithKafkaProperties() {
+ client.resource(new ConfigMapBuilder()
+ .withNewMetadata()
+ .withName("cm-1")
+ .withNamespace("ns2")
+ .addToLabels(ConsoleResource.MANAGEMENT_LABEL)
+ .endMetadata()
+ .addToData("x-consumer-prop-name", "x-consumer-prop-value")
+ .build())
+ .serverSideApply();
+
+ client.resource(new SecretBuilder()
+ .withNewMetadata()
+ .withName("scrt-1")
+ .withNamespace("ns2")
+ .addToLabels(ConsoleResource.MANAGEMENT_LABEL)
+ .endMetadata()
+ .addToData("x-producer-prop-name",
+ Base64.getEncoder().encodeToString("x-producer-prop-value".getBytes()))
+ .build())
+ .serverSideApply();
+
+ Console consoleCR = new ConsoleBuilder()
+ .withMetadata(new ObjectMetaBuilder()
+ .withName("console-1")
+ .withNamespace("ns2")
+ .build())
+ .withNewSpec()
+ .withHostname("example.com")
+ .addNewKafkaCluster()
+ .withId("custom-id")
+ .withName(kafkaCR.getMetadata().getName())
+ .withNewProperties()
+ .addNewValue()
+ .withName("x-prop-name")
+ .withValue("x-prop-value")
+ .endValue()
+ .endProperties()
+ .withNewAdminProperties()
+ .addNewValue()
+ .withName("x-admin-prop-name")
+ .withValue("x-admin-prop-value")
+ .endValue()
+ .endAdminProperties()
+ .withNewConsumerProperties()
+ .addNewValuesFrom()
+ .withPrefix("extra-")
+ .withNewConfigMapRef("cm-1", false)
+ .endValuesFrom()
+ .addNewValuesFrom()
+ .withNewConfigMapRef("cm-2", true)
+ .endValuesFrom()
+ .endConsumerProperties()
+ .withNewProducerProperties()
+ .addNewValuesFrom()
+ .withNewSecretRef("scrt-1", false)
+ .endValuesFrom()
+ .endProducerProperties()
+ .endKafkaCluster()
+ .endSpec()
+ .build();
+
+ client.resource(consoleCR).create();
+
+ await().ignoreException(NullPointerException.class).atMost(LIMIT).untilAsserted(() -> {
+ var consoleSecret = client.secrets().inNamespace("ns2").withName("console-1-" + ConsoleSecret.NAME).get();
+ assertNotNull(consoleSecret);
+ String configEncoded = consoleSecret.getData().get("console-config.yaml");
+ byte[] configDecoded = Base64.getDecoder().decode(configEncoded);
+ ConsoleConfig config = new ObjectMapper().readValue(configDecoded, ConsoleConfig.class);
+ var kafkaConfig = config.getKafka().getClusters().get(0);
+ assertEquals("x-prop-value", kafkaConfig.getProperties().get("x-prop-name"));
+ assertEquals("x-admin-prop-value", kafkaConfig.getAdminProperties().get("x-admin-prop-name"));
+ assertEquals("x-consumer-prop-value", kafkaConfig.getConsumerProperties().get("extra-x-consumer-prop-name"));
+ assertEquals("x-producer-prop-value", kafkaConfig.getProducerProperties().get("x-producer-prop-name"));
+ });
+ }
+
// Utility
private Deployment setReady(Deployment deployment) {
diff --git a/pom.xml b/pom.xml
index 83fece39c..97adf458a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,6 +37,11 @@
https://sonarcloud.io
streamshub
api/src/main/java/com/github/streamshub/console/api/support/TrustAllCertificateManager.java
+
+
+ common/src/main/java/com/github/streamshub/console/config/*.java,
+ operator/src/main/java/com/github/streamshub/console/api/v1alpha1/**/*.java
+
e1,e2
java:S6813
diff --git a/ui/api/kafka/actions.ts b/ui/api/kafka/actions.ts
index c01eb8d0e..e1927e1b7 100644
--- a/ui/api/kafka/actions.ts
+++ b/ui/api/kafka/actions.ts
@@ -76,7 +76,7 @@ export async function getKafkaClusterKpis(
): Promise<{ cluster: ClusterDetail; kpis: ClusterKpis | null } | null> {
try {
const cluster = await getKafkaCluster(clusterId);
- if (!cluster) {
+ if (!cluster?.attributes?.namespace) {
return null;
}
@@ -238,7 +238,7 @@ export async function getKafkaClusterMetrics(
try {
const cluster = await getKafkaCluster(clusterId);
- if (!cluster || !prom) {
+ if (!cluster?.attributes?.namespace || !prom) {
return null;
}
@@ -246,7 +246,7 @@ export async function getKafkaClusterMetrics(
await Promise.all(
metrics.map((m) =>
getRangeByNodeId(
- cluster.attributes.namespace,
+ cluster.attributes.namespace!,
cluster.attributes.name,
cluster.attributes.nodePools?.join("|") ?? "",
m,
@@ -303,7 +303,7 @@ export async function getKafkaTopicMetrics(
try {
const cluster = await getKafkaCluster(clusterId);
- if (!cluster || !prom) {
+ if (!cluster?.attributes?.namespace || !prom) {
return null;
}
@@ -311,7 +311,7 @@ export async function getKafkaTopicMetrics(
await Promise.all(
metrics.map((m) =>
getRangeByNodeId(
- cluster.attributes.namespace,
+ cluster.attributes.namespace!,
cluster.attributes.name,
cluster.attributes.nodePools?.join("|") ?? "",
m,
diff --git a/ui/api/kafka/schema.ts b/ui/api/kafka/schema.ts
index ba05935bf..913986de9 100644
--- a/ui/api/kafka/schema.ts
+++ b/ui/api/kafka/schema.ts
@@ -15,8 +15,8 @@ export const ClusterListSchema = z.object({
}),
attributes: z.object({
name: z.string(),
- namespace: z.string(),
- kafkaVersion: z.string().optional(),
+ namespace: z.string().nullable().optional(),
+ kafkaVersion: z.string().nullable().optional(),
}),
});
export const ClustersResponseSchema = z.object({
@@ -28,10 +28,10 @@ const ClusterDetailSchema = z.object({
type: z.literal("kafkas"),
attributes: z.object({
name: z.string(),
- namespace: z.string(),
- creationTimestamp: z.string(),
- status: z.string(),
- kafkaVersion: z.string().optional(),
+ namespace: z.string().nullable().optional(),
+ creationTimestamp: z.string().nullable().optional(),
+ status: z.string().nullable().optional(),
+ kafkaVersion: z.string().nullable().optional(),
nodes: z.array(NodeSchema),
controller: NodeSchema,
authorizedOperations: z.array(z.string()),
@@ -41,7 +41,7 @@ const ClusterDetailSchema = z.object({
bootstrapServers: z.string().nullable(),
authType: z.string().nullable(),
}),
- ),
+ ).nullable().optional(),
conditions: z.array(
z.object({
type: z.string().optional(),
@@ -50,7 +50,7 @@ const ClusterDetailSchema = z.object({
message: z.string().optional(),
lastTransitionTime: z.string().optional(),
}),
- ),
+ ).nullable().optional(),
nodePools: z.array(z.string()).optional().nullable(),
}),
});
diff --git a/ui/app/[locale]/home/ClustersTable.tsx b/ui/app/[locale]/home/ClustersTable.tsx
index dc6f45863..f3337df5c 100644
--- a/ui/app/[locale]/home/ClustersTable.tsx
+++ b/ui/app/[locale]/home/ClustersTable.tsx
@@ -91,9 +91,9 @@ export function ClustersTable({
{t("ClustersTable.connection_not_configured")}
);
case "version":
- return {row.attributes.kafkaVersion ?? "n/a"} | ;
+ return {row.attributes.kafkaVersion ?? "Not Available"} | ;
case "namespace":
- return {row.attributes.namespace} | ;
+ return {row.attributes.namespace ?? "N/A"} | ;
}
}}
renderActions={({ ActionsColumn, row }) => (