From 20c04bb1734d5ea82f5b370061a9ad2e86564d20 Mon Sep 17 00:00:00 2001 From: Andrea Peruffo Date: Mon, 21 Oct 2024 15:55:16 +0100 Subject: [PATCH] Add a basic Sql Datasource for operator (#5363) --- .../resource/app/AppDeploymentResource.java | 28 ++++++++++ .../operator/it/SqlDatasourceITTest.java | 53 +++++++++++++++++++ .../test/resources/k8s/example-postgres.yaml | 44 +++++++++++++++ .../api/v1/ApicurioRegistry3SpecApp.java | 9 +++- .../registry/operator/api/v1/spec/Sql.java | 30 +++++++++++ .../operator/api/v1/spec/sql/Datasource.java | 51 ++++++++++++++++++ 6 files changed, 214 insertions(+), 1 deletion(-) create mode 100644 operator/controller/src/test/java/io/apicurio/registry/operator/it/SqlDatasourceITTest.java create mode 100644 operator/controller/src/test/resources/k8s/example-postgres.yaml create mode 100644 operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/Sql.java create mode 100644 operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/sql/Datasource.java diff --git a/operator/controller/src/main/java/io/apicurio/registry/operator/resource/app/AppDeploymentResource.java b/operator/controller/src/main/java/io/apicurio/registry/operator/resource/app/AppDeploymentResource.java index 282bd7eb0a..9b7e5b9c16 100644 --- a/operator/controller/src/main/java/io/apicurio/registry/operator/resource/app/AppDeploymentResource.java +++ b/operator/controller/src/main/java/io/apicurio/registry/operator/resource/app/AppDeploymentResource.java @@ -2,6 +2,7 @@ import io.apicurio.registry.operator.OperatorException; import io.apicurio.registry.operator.api.v1.ApicurioRegistry3; +import io.apicurio.registry.operator.api.v1.spec.Sql; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.EnvVar; import io.fabric8.kubernetes.api.model.EnvVarBuilder; @@ -33,6 +34,12 @@ public class AppDeploymentResource extends CRUDKubernetesDependentResource map, Sql sql) { + if (sql != null && sql.getDatasource() != null) { + var datasource = sql.getDatasource(); + + addEnvVar(map, new EnvVarBuilder().withName(ENV_APICURIO_STORAGE_KIND).withValue("sql").build()); + addEnvVar(map, new EnvVarBuilder().withName(ENV_APICURIO_STORAGE_SQL_KIND).withValue("postgresql") + .build()); + + addEnvVar(map, new EnvVarBuilder().withName(ENV_APICURIO_DATASOURCE_URL) + .withValue(datasource.getUrl()).build()); + addEnvVar(map, new EnvVarBuilder().withName(ENV_APICURIO_DATASOURCE_USERNAME) + .withValue(datasource.getUsername()).build()); + addEnvVar(map, new EnvVarBuilder().withName(ENV_APICURIO_DATASOURCE_PASSWORD) + .withValue(datasource.getPassword()).build()); + } else { + log.info("No SQL datasource configured"); + } + } + public static void addEnvVar(Map map, EnvVar envVar) { if (!map.containsKey(envVar.getName())) { map.put(envVar.getName(), envVar); diff --git a/operator/controller/src/test/java/io/apicurio/registry/operator/it/SqlDatasourceITTest.java b/operator/controller/src/test/java/io/apicurio/registry/operator/it/SqlDatasourceITTest.java new file mode 100644 index 0000000000..dde02077e4 --- /dev/null +++ b/operator/controller/src/test/java/io/apicurio/registry/operator/it/SqlDatasourceITTest.java @@ -0,0 +1,53 @@ +package io.apicurio.registry.operator.it; + +import io.apicurio.registry.operator.api.v1.ApicurioRegistry3; +import io.apicurio.registry.operator.api.v1.spec.Sql; +import io.apicurio.registry.operator.api.v1.spec.sql.Datasource; +import io.apicurio.registry.operator.resource.ResourceFactory; +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +@QuarkusTest +public class SqlDatasourceITTest extends ITBase { + + private static final Logger log = LoggerFactory.getLogger(SqlDatasourceITTest.class); + + @Test + void testSqlDatasource() { + client.load(SqlDatasourceITTest.class.getResourceAsStream("/k8s/example-postgres.yaml")).create(); + // await for postgres to be available + await().ignoreExceptions().until(() -> (1 == client.apps().statefulSets().inNamespace(namespace) + .withName("postgresql-db").get().getStatus().getReadyReplicas())); + + var registry = ResourceFactory.deserialize("/k8s/examples/simple.apicurioregistry3.yaml", + ApicurioRegistry3.class); + registry.getMetadata().setNamespace(namespace); + var sql = new Sql(); + registry.getSpec().getApp().setSql(sql); + var datasource = new Datasource(); + sql.setDatasource(datasource); + datasource.setUrl("jdbc:postgresql://postgres-db:5432/apicurio"); + datasource.setUsername("postgres-username"); + datasource.setPassword("postgres-password"); + + client.resource(registry).create(); + + await().ignoreExceptions().until(() -> { + assertThat(client.apps().deployments().inNamespace(namespace) + .withName(registry.getMetadata().getName() + "-app-deployment").get().getStatus() + .getReadyReplicas().intValue()).isEqualTo(1); + var podName = client.pods().inNamespace(namespace).list().getItems().stream() + .map(pod -> pod.getMetadata().getName()) + .filter(podN -> podN.startsWith("simple-app-deployment")).findFirst().get(); + assertThat(client.pods().inNamespace(namespace).withName(podName).getLog()) + .contains("Database type: postgresql"); + + return true; + }); + } +} diff --git a/operator/controller/src/test/resources/k8s/example-postgres.yaml b/operator/controller/src/test/resources/k8s/example-postgres.yaml new file mode 100644 index 0000000000..b7ccb25133 --- /dev/null +++ b/operator/controller/src/test/resources/k8s/example-postgres.yaml @@ -0,0 +1,44 @@ +# PostgreSQL StatefulSet +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: postgresql-db +spec: + serviceName: postgresql-db-service + selector: + matchLabels: + app: postgresql-db + replicas: 1 + template: + metadata: + labels: + app: postgresql-db + spec: + containers: + - name: postgresql-db + image: quay.io/sclorg/postgresql-15-c9s:latest + volumeMounts: + - mountPath: /var/lib/pgsql/data + name: cache-volume + env: + - name: POSTGRESQL_USER + value: postgres-username + - name: POSTGRESQL_PASSWORD + value: postgres-password + - name: POSTGRESQL_DATABASE + value: apicurio + volumes: + - name: cache-volume + emptyDir: {} +--- +# PostgreSQL StatefulSet Service +apiVersion: v1 +kind: Service +metadata: + name: postgres-db +spec: + selector: + app: postgresql-db + ports: + - port: 5432 + targetPort: 5432 diff --git a/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/ApicurioRegistry3SpecApp.java b/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/ApicurioRegistry3SpecApp.java index f2f8802dcf..4c4d49419e 100644 --- a/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/ApicurioRegistry3SpecApp.java +++ b/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/ApicurioRegistry3SpecApp.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.JsonDeserializer.None; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import io.apicurio.registry.operator.api.v1.spec.Sql; import io.fabric8.kubernetes.api.model.EnvVar; import io.fabric8.kubernetes.api.model.KubernetesResource; import lombok.*; @@ -12,7 +13,7 @@ import java.util.List; @JsonInclude(Include.NON_NULL) -@JsonPropertyOrder({ "env", "host" }) +@JsonPropertyOrder({ "env", "host", "sql" }) @JsonDeserialize(using = None.class) @NoArgsConstructor @AllArgsConstructor(access = AccessLevel.PRIVATE) @@ -47,4 +48,10 @@ public class ApicurioRegistry3SpecApp implements KubernetesResource { If you create the Ingress manually, you have to manually set the REGISTRY_API_URL environment variable for the backend component.""") @JsonSetter(nulls = Nulls.SKIP) private String host; + + @JsonProperty("sql") + @JsonPropertyDescription(""" + Configuration of Apicurio Registry SQL storage.""") + @JsonSetter(nulls = Nulls.SKIP) + private Sql sql; } diff --git a/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/Sql.java b/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/Sql.java new file mode 100644 index 0000000000..6082860895 --- /dev/null +++ b/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/Sql.java @@ -0,0 +1,30 @@ +package io.apicurio.registry.operator.api.v1.spec; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import io.apicurio.registry.operator.api.v1.spec.sql.Datasource; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ "datasource" }) +@JsonDeserialize(using = JsonDeserializer.None.class) +@Getter +@Setter +@ToString +public class Sql { + + @JsonProperty("datasource") + @JsonPropertyDescription(""" + SQL data source.""") + @JsonSetter(nulls = Nulls.SKIP) + private Datasource datasource; + +} diff --git a/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/sql/Datasource.java b/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/sql/Datasource.java new file mode 100644 index 0000000000..db1e868277 --- /dev/null +++ b/operator/model/src/main/java/io/apicurio/registry/operator/api/v1/spec/sql/Datasource.java @@ -0,0 +1,51 @@ +package io.apicurio.registry.operator.api.v1.spec.sql; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.Nulls; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ "url", "username", "password" }) +@JsonDeserialize(using = JsonDeserializer.None.class) +@Getter +@Setter +@ToString +public class Datasource { + + @JsonProperty("url") + @JsonPropertyDescription(""" + Data source URL: \\n URL of the PostgreSQL + database, for example: `jdbc:postgresql://..svc:5432/`.""") + @JsonSetter(nulls = Nulls.SKIP) + private String url; + + @JsonProperty("username") + @JsonPropertyDescription(""" + Data source username.""") + @JsonSetter(nulls = Nulls.SKIP) + private String username; + + @JsonProperty("password") + @JsonPropertyDescription(""" + Data source password.""") + @JsonSetter(nulls = Nulls.SKIP) + private String password; + + // TODO: support values from Secrets: + // + // Proposal to support secrets, add alternative fields like: + // @JsonProperty("passwordFrom") + // @JsonPropertyDescription(""" + // Data source password from Secret/ConfigMap/...""") + // @JsonSetter(nulls = Nulls.SKIP) + // private EnvVarSource passwordFrom; + +}