Skip to content

Commit 841b02d

Browse files
committed
validate K8s resource references against the RFC 1123 hostname format Kubernetes uses
1 parent 31860e8 commit 841b02d

5 files changed

Lines changed: 89 additions & 0 deletions

File tree

operator/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ dependencies {
2020
* Fabric8 Kubernetes Client
2121
*/
2222
implementation("io.fabric8:generator-annotations")
23+
implementation("io.fabric8:crd-generator-api-v2")
2324

2425
/**
2526
* jOOQ

operator/src/main/java/it/aboutbits/postgresql/core/ClusterReference.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package it.aboutbits.postgresql.core;
22

3+
import io.fabric8.crdv2.generator.v1.SchemaCustomizer;
34
import io.fabric8.generator.annotation.Required;
45
import io.fabric8.generator.annotation.ValidationRule;
56
import lombok.Getter;
@@ -10,6 +11,7 @@
1011
@NullMarked
1112
@Getter
1213
@Setter
14+
@SchemaCustomizer(HostnameRFC1123Customizer.class)
1315
public class ClusterReference {
1416
@Required
1517
@ValidationRule(
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package it.aboutbits.postgresql.core;
2+
3+
import io.fabric8.crdv2.generator.v1.SchemaCustomizer;
4+
import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps;
5+
import io.fabric8.kubernetes.client.utils.KubernetesSerialization;
6+
import org.jspecify.annotations.NullMarked;
7+
8+
import java.util.Arrays;
9+
import java.util.Set;
10+
import java.util.stream.Collectors;
11+
12+
/// A [SchemaCustomizer.Customizer] that sets the `format` of string properties
13+
/// to `"hostname"` (RFC 1123) in the generated CRD JSON Schema.
14+
///
15+
/// This customizer is intended to be used with the
16+
/// [@SchemaCustomizer][SchemaCustomizer] annotation on a class whose properties
17+
/// should be validated as RFC 1123 hostnames by the Kubernetes API server.
18+
///
19+
/// ### Behavior
20+
///
21+
/// - If `input` is **blank** (the default), the `"hostname"` format
22+
/// is applied to **all** string properties of the annotated class.
23+
/// - If `input` contains a **comma-separated list** of field names,
24+
/// the format is applied **only** to the specified properties.
25+
///
26+
/// ### Usage examples
27+
///
28+
/// **Apply to all string properties:**
29+
///
30+
/// ```java
31+
/// @SchemaCustomizer(HostnameRFC1123Customizer.class)
32+
/// public class SecretRef {
33+
/// private String name = ""; // gets format: "hostname"
34+
/// private String namespace; // gets format: "hostname"
35+
/// }
36+
/// ```
37+
///
38+
/// **Apply to specific properties only:**
39+
///
40+
/// ```java
41+
/// @SchemaCustomizer(value = HostnameRFC1123Customizer.class, input = "host")
42+
/// public class ClusterConnectionSpec {
43+
/// private String host = ""; // gets format: "hostname"
44+
/// private String anotherHost = ""; // gets format: "hostname"
45+
/// private String database = ""; // unchanged
46+
/// }
47+
/// ```
48+
///
49+
/// @see SchemaCustomizer
50+
/// @see SchemaCustomizer.Customizer
51+
@NullMarked
52+
public class HostnameRFC1123Customizer implements SchemaCustomizer.Customizer {
53+
@Override
54+
public JSONSchemaProps apply(
55+
JSONSchemaProps jsonSchemaProps,
56+
String input,
57+
KubernetesSerialization kubernetesSerialization
58+
) {
59+
var properties = jsonSchemaProps.getProperties();
60+
if (properties == null) {
61+
return jsonSchemaProps;
62+
}
63+
64+
var targetFields = input.isBlank()
65+
? Set.<String>of()
66+
: Arrays.stream(input.split(","))
67+
.map(String::trim)
68+
.collect(Collectors.toSet());
69+
70+
for (var entry : properties.entrySet()) {
71+
var prop = entry.getValue();
72+
if ("string".equals(prop.getType())) {
73+
if (targetFields.isEmpty() || targetFields.contains(entry.getKey())) {
74+
prop.setFormat("hostname");
75+
}
76+
}
77+
}
78+
79+
return jsonSchemaProps;
80+
}
81+
}

operator/src/main/java/it/aboutbits/postgresql/core/SecretRef.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package it.aboutbits.postgresql.core;
22

3+
import io.fabric8.crdv2.generator.v1.SchemaCustomizer;
34
import io.fabric8.generator.annotation.Required;
45
import io.fabric8.generator.annotation.ValidationRule;
56
import lombok.Getter;
@@ -10,6 +11,7 @@
1011
@NullMarked
1112
@Getter
1213
@Setter
14+
@SchemaCustomizer(HostnameRFC1123Customizer.class)
1315
public class SecretRef {
1416
@Required
1517
@ValidationRule(

operator/src/main/java/it/aboutbits/postgresql/crd/clusterconnection/ClusterConnectionSpec.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package it.aboutbits.postgresql.crd.clusterconnection;
22

3+
import io.fabric8.crdv2.generator.v1.SchemaCustomizer;
34
import io.fabric8.generator.annotation.Max;
45
import io.fabric8.generator.annotation.Min;
56
import io.fabric8.generator.annotation.Required;
67
import io.fabric8.generator.annotation.ValidationRule;
78
import it.aboutbits.postgresql.core.SecretRef;
9+
import it.aboutbits.postgresql.core.HostnameRFC1123Customizer;
810
import lombok.Getter;
911
import lombok.Setter;
1012
import org.jspecify.annotations.NullMarked;
@@ -15,6 +17,7 @@
1517
@NullMarked
1618
@Getter
1719
@Setter
20+
@SchemaCustomizer(value = HostnameRFC1123Customizer.class, input = "host")
1821
public class ClusterConnectionSpec {
1922
@Required
2023
@ValidationRule(

0 commit comments

Comments
 (0)