Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ org.gradle.logging.level=INFO

# Quarkus
quarkusPluginId=io.quarkus
quarkusPluginVersion=3.31.2
quarkusPluginVersion=3.32.1
# https://mvnrepository.com/artifact/io.quarkus.platform/quarkus-bom
quarkusPlatformGroupId=io.quarkus.platform
quarkusPlatformArtifactId=quarkus-bom
quarkusPlatformVersion=3.31.2
quarkusPlatformVersion=3.32.1
systemProp.quarkus.analytics.disabled=true
12 changes: 6 additions & 6 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[versions]
## AboutBits Libraries ##
checkstyleConfig = "2.0.0-RC1"
checkstyleConfig = "2.0.0-RC2"

# Axion Release Plugin #
axionReleasePlugin = "1.21.1"
Expand All @@ -9,15 +9,15 @@ axionReleasePlugin = "1.21.1"
jooq = "3.20.11"
jSpecify = "1.0.0"
lombok = "1.18.42"
postgresql = "42.7.9"
quarkiverse-helm = "1.2.7"
postgresql = "42.7.10"
quarkiverse-helm = "1.3.0"
scram-client = "3.2"

## Testing ##
assertj = "3.27.7"
checkstyle = "13.1.0"
datafaker = "2.5.3"
errorProne = "2.46.0"
checkstyle = "13.2.0"
datafaker = "2.5.4"
errorProne = "2.47.0"
errorPronePlugin = "5.0.0"
nullAway = "0.13.1"

Expand Down
1 change: 1 addition & 0 deletions operator/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependencies {
* Fabric8 Kubernetes Client
*/
implementation("io.fabric8:generator-annotations")
implementation("io.fabric8:crd-generator-api-v2")

/**
* jOOQ
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@ public HealthCheckResponse call() {
var connections = kubernetesClient.resources(ClusterConnection.class).list().getItems();

boolean allUp = connections.stream()
.allMatch(connection -> checkInstance(
.map(connection -> checkInstance(
connection,
builder
));
))
.reduce(true, Boolean::logicalAnd);

return builder.status(allUp).build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package it.aboutbits.postgresql.core;

import io.fabric8.crdv2.generator.v1.SchemaCustomizer;
import io.fabric8.generator.annotation.Max;
import io.fabric8.generator.annotation.Required;
import io.fabric8.generator.annotation.ValidationRule;
import it.aboutbits.postgresql.core.schema_customizer.KubernetesNameCustomizer;
import lombok.Getter;
import lombok.Setter;
import org.jspecify.annotations.NullMarked;
Expand All @@ -10,15 +13,18 @@
@NullMarked
@Getter
@Setter
@SchemaCustomizer(KubernetesNameCustomizer.class)
public class ClusterReference {
@Required
@Max(63)
@ValidationRule(
value = "self.trim().size() > 0",
message = "The ClusterReference name must not be empty."
)
private String name = "";

@Nullable
@Max(63)
@io.fabric8.generator.annotation.Nullable
private String namespace;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import jakarta.enterprise.context.ApplicationScoped;
import lombok.RequiredArgsConstructor;
import org.jooq.CloseableDSLContext;
import org.jooq.exception.DataAccessException;
import org.jooq.impl.DSL;
import org.jspecify.annotations.NullMarked;

Expand All @@ -21,7 +22,7 @@ public class PostgreSQLContextFactory {
private final KubernetesClient kubernetesClient;

/// Create a DSLContext with a JDBC connection to the PostgreSQL maintenance database.
public CloseableDSLContext getDSLContext(ClusterConnection clusterConnection) {
public CloseableDSLContext getDSLContext(ClusterConnection clusterConnection) throws DataAccessException {
return getDSLContext(
clusterConnection,
clusterConnection.getSpec().getDatabase()
Expand All @@ -32,7 +33,7 @@ public CloseableDSLContext getDSLContext(ClusterConnection clusterConnection) {
public CloseableDSLContext getDSLContext(
ClusterConnection clusterConnection,
String database
) {
) throws DataAccessException {
var credentials = kubernetesService.getSecretRefCredentials(
kubernetesClient,
clusterConnection
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package it.aboutbits.postgresql.core;

import io.fabric8.crdv2.generator.v1.SchemaCustomizer;
import io.fabric8.generator.annotation.Max;
import io.fabric8.generator.annotation.Required;
import io.fabric8.generator.annotation.ValidationRule;
import it.aboutbits.postgresql.core.schema_customizer.KubernetesNameCustomizer;
import lombok.Getter;
import lombok.Setter;
import org.jspecify.annotations.NullMarked;
Expand All @@ -10,8 +13,10 @@
@NullMarked
@Getter
@Setter
@SchemaCustomizer(KubernetesNameCustomizer.class)
public class SecretRef {
@Required
@Max(63)
@ValidationRule(
value = "self.trim().size() > 0",
message = "The SecretRef name must not be empty."
Expand All @@ -23,6 +28,7 @@ public class SecretRef {
* If it is null, it means the Secret is in the same namespace as the resource referencing it.
*/
@Nullable
@Max(63)
@io.fabric8.generator.annotation.Nullable
private String namespace;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package it.aboutbits.postgresql.core.schema_customizer;

import io.fabric8.crdv2.generator.v1.SchemaCustomizer;
import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps;
import io.fabric8.kubernetes.client.utils.KubernetesSerialization;
import org.jspecify.annotations.NullMarked;

import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/// A [SchemaCustomizer.Customizer] that sets the `format` of string properties
/// to `{"anyOf":[{"format":"hostname"},{"format":"ipv4"},{"format":"ipv6"}]}`
/// in the generated CRD JSON Schema.
///
/// This customizer is intended to be used with the
/// [@SchemaCustomizer][SchemaCustomizer] annotation on a class whose properties
/// should be validated to valid hosts defined.
///
/// ### Behavior
///
/// - If `input` is **blank** (the default), the `"hostname"` format
/// is applied to **all** string properties of the annotated class.
/// - If `input` contains a **comma-separated list** of field names,
/// the format is applied **only** to the specified properties.
///
/// ### Usage examples
///
/// **Apply to all string properties:**
///
/// ```java
/// @SchemaCustomizer(value = HostCustomizer.class)
/// public class ClusterConnectionSpec {
/// private String host = ""; // gets custom format
/// private String anotherHost = ""; // gets custom format
/// }
/// ```
///
/// **Apply to specific properties only:**
///
/// ```java
/// @SchemaCustomizer(value = HostCustomizer.class, input = "host,anotherHost")
/// public class ClusterConnectionSpec {
/// private String host = ""; // gets custom format
/// private String anotherHost = ""; // gets custom format
/// private String unchangedHost = ""; // unchanged
/// }
/// ```
///
/// @see SchemaCustomizer
/// @see SchemaCustomizer.Customizer
@NullMarked
public class HostCustomizer implements SchemaCustomizer.Customizer {
@Override
public JSONSchemaProps apply(
JSONSchemaProps jsonSchemaProps,
String input,
KubernetesSerialization kubernetesSerialization
) {
var properties = jsonSchemaProps.getProperties();
if (properties == null) {
return jsonSchemaProps;
}

var targetFields = input.isBlank()
? Set.<String>of()
: Arrays.stream(input.split(","))
.map(String::trim)
.collect(Collectors.toSet());

for (var entry : properties.entrySet()) {
var prop = entry.getValue();
if ("string".equals(prop.getType())
&& (targetFields.isEmpty() || targetFields.contains(entry.getKey()))
) {
prop.setFormat(null);

var hostnameProp = new JSONSchemaProps();
hostnameProp.setFormat("hostname");

var ipv4Prop = new JSONSchemaProps();
ipv4Prop.setFormat("ipv4");

var ipv6Prop = new JSONSchemaProps();
ipv6Prop.setFormat("ipv6");

prop.setAnyOf(List.of(
hostnameProp,
ipv4Prop,
ipv6Prop
));
}
}

return jsonSchemaProps;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package it.aboutbits.postgresql.core.schema_customizer;

import io.fabric8.crdv2.generator.v1.SchemaCustomizer;
import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps;
import io.fabric8.kubernetes.client.utils.KubernetesSerialization;
import org.jspecify.annotations.NullMarked;

import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;

/// A [SchemaCustomizer.Customizer] that adds a Kubernetes name validation
/// `pattern` (RFC 1123 DNS label) to string properties in the generated CRD
/// JSON Schema.
///
/// The pattern enforces:
/// - Contain at most 63 characters
/// - Contain only lowercase alphanumeric characters or '-'
/// - Start with an alphabetic character
/// - End with an alphanumeric character
///
/// This customizer is intended to be used with the
/// [@SchemaCustomizer][SchemaCustomizer] annotation on a class whose string
/// properties represent Kubernetes resource names.
///
/// ### Behavior
///
/// - If `input` is **blank** (the default), the `"hostname"` format
/// is applied to **all** string properties of the annotated class.
/// - If `input` contains a **comma-separated list** of field names,
/// the format is applied **only** to the specified properties.
///
/// ### Usage examples
///
/// **Apply to all string properties:**
///
/// ```java
/// @SchemaCustomizer(KubernetesNameCustomizer.class)
/// public class SecretRef {
/// private String name = ""; // gets pattern: Kubernetes name regex
/// private String namespace; // gets pattern: Kubernetes name regex
/// }
/// ```
///
/// **Apply to specific properties only:**
///
/// ```java
/// @SchemaCustomizer(value = KubernetesNameCustomizer.class, input = "name,anotherName")
/// public class SecretRef {
/// private String name = ""; // gets pattern: Kubernetes name regex
/// private String anotherName = ""; // gets pattern: Kubernetes name regex
/// private String namespace; // unchanged
/// }
/// ```
///
/// @see SchemaCustomizer
/// @see SchemaCustomizer.Customizer
@NullMarked
public class KubernetesNameCustomizer implements SchemaCustomizer.Customizer {
static final String KUBERNETES_NAME_PATTERN = "^[a-z]([a-z0-9\\-]{0,61}[a-z0-9])?$";

@Override
public JSONSchemaProps apply(
JSONSchemaProps jsonSchemaProps,
String input,
KubernetesSerialization kubernetesSerialization
) {
var properties = jsonSchemaProps.getProperties();
if (properties == null) {
return jsonSchemaProps;
}

var targetFields = input.isBlank()
? Set.<String>of()
: Arrays.stream(input.split(","))
.map(String::trim)
.collect(Collectors.toSet());

for (var entry : properties.entrySet()) {
var prop = entry.getValue();
if ("string".equals(prop.getType())
&& (targetFields.isEmpty() || targetFields.contains(entry.getKey()))
) {
prop.setPattern(KUBERNETES_NAME_PATTERN);
}
}

return jsonSchemaProps;
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package it.aboutbits.postgresql.crd.clusterconnection;

import io.fabric8.crdv2.generator.v1.SchemaCustomizer;
import io.fabric8.generator.annotation.Max;
import io.fabric8.generator.annotation.Min;
import io.fabric8.generator.annotation.Required;
import io.fabric8.generator.annotation.ValidationRule;
import it.aboutbits.postgresql.core.SecretRef;
import it.aboutbits.postgresql.core.schema_customizer.HostCustomizer;
import lombok.Getter;
import lombok.Setter;
import org.jspecify.annotations.NullMarked;
Expand All @@ -15,6 +17,7 @@
@NullMarked
@Getter
@Setter
@SchemaCustomizer(value = HostCustomizer.class, input = "host")
public class ClusterConnectionSpec {
@Required
@ValidationRule(
Expand Down
Loading