Skip to content

Commit fffa11c

Browse files
authored
feat: support annotations (#111)
Signed-off-by: CG <cgpoh76@gmail.com>
1 parent 18002f1 commit fffa11c

8 files changed

Lines changed: 412 additions & 32 deletions

File tree

api/src/main/java/io/strimzi/kafka/access/model/KafkaAccessSpec.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class KafkaAccessSpec {
2121
private KafkaReference kafka;
2222
private KafkaUserReference user;
2323
private String secretName;
24+
private KafkaAccessTemplate template;
2425

2526
/**
2627
* Gets the KafkaReference instance
@@ -75,4 +76,22 @@ public String getSecretName() {
7576
public void setSecretName(String secretName) {
7677
this.secretName = secretName;
7778
}
79+
80+
/**
81+
* Gets the template for customizing generated resources
82+
*
83+
* @return The KafkaAccessTemplate instance
84+
*/
85+
public KafkaAccessTemplate getTemplate() {
86+
return template;
87+
}
88+
89+
/**
90+
* Sets the template for customizing generated resources
91+
*
92+
* @param template The KafkaAccessTemplate model
93+
*/
94+
public void setTemplate(final KafkaAccessTemplate template) {
95+
this.template = template;
96+
}
7897
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright Strimzi authors.
3+
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
4+
*/
5+
package io.strimzi.kafka.access.model;
6+
7+
import io.strimzi.api.kafka.model.common.Constants;
8+
import io.sundr.builder.annotations.Buildable;
9+
10+
/**
11+
* Template for KafkaAccess resources (e.g., Secret template)
12+
*/
13+
@Buildable(
14+
editableEnabled = false,
15+
builderPackage = Constants.FABRIC8_KUBERNETES_API
16+
)
17+
public class KafkaAccessTemplate {
18+
19+
private SecretTemplate secret;
20+
21+
/**
22+
* Gets the Secret template
23+
*
24+
* @return The SecretTemplate instance
25+
*/
26+
public SecretTemplate getSecret() {
27+
return secret;
28+
}
29+
30+
/**
31+
* Sets the Secret template
32+
*
33+
* @param secret The SecretTemplate model
34+
*/
35+
public void setSecret(final SecretTemplate secret) {
36+
this.secret = secret;
37+
}
38+
}
39+
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright Strimzi authors.
3+
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
4+
*/
5+
package io.strimzi.kafka.access.model;
6+
7+
import io.strimzi.api.kafka.model.common.Constants;
8+
import io.sundr.builder.annotations.Buildable;
9+
10+
import java.util.Map;
11+
12+
/**
13+
* Template for Kubernetes resource metadata (labels, annotations)
14+
*/
15+
@Buildable(
16+
editableEnabled = false,
17+
builderPackage = Constants.FABRIC8_KUBERNETES_API
18+
)
19+
public class MetadataTemplate {
20+
21+
private Map<String, String> labels;
22+
private Map<String, String> annotations;
23+
24+
/**
25+
* Gets the labels
26+
*
27+
* @return A map of labels
28+
*/
29+
public Map<String, String> getLabels() {
30+
return labels;
31+
}
32+
33+
/**
34+
* Sets the labels
35+
*
36+
* @param labels A map of labels
37+
*/
38+
public void setLabels(final Map<String, String> labels) {
39+
this.labels = labels;
40+
}
41+
42+
/**
43+
* Gets the annotations
44+
*
45+
* @return A map of annotations
46+
*/
47+
public Map<String, String> getAnnotations() {
48+
return annotations;
49+
}
50+
51+
/**
52+
* Sets the annotations
53+
*
54+
* @param annotations A map of annotations
55+
*/
56+
public void setAnnotations(final Map<String, String> annotations) {
57+
this.annotations = annotations;
58+
}
59+
}
60+
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright Strimzi authors.
3+
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
4+
*/
5+
package io.strimzi.kafka.access.model;
6+
7+
import io.strimzi.api.kafka.model.common.Constants;
8+
import io.sundr.builder.annotations.Buildable;
9+
10+
/**
11+
* Template for Secret
12+
*/
13+
@Buildable(
14+
editableEnabled = false,
15+
builderPackage = Constants.FABRIC8_KUBERNETES_API
16+
)
17+
public class SecretTemplate {
18+
19+
private MetadataTemplate metadata;
20+
21+
/**
22+
* Gets the metadata template
23+
*
24+
* @return The MetadataTemplate instance
25+
*/
26+
public MetadataTemplate getMetadata() {
27+
return metadata;
28+
}
29+
30+
/**
31+
* Sets the metadata template
32+
*
33+
* @param metadata The MetadataTemplate model
34+
*/
35+
public void setMetadata(final MetadataTemplate metadata) {
36+
this.metadata = metadata;
37+
}
38+
}
39+

operator/src/main/java/io/strimzi/kafka/access/KafkaAccessReconciler.java

Lines changed: 108 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -106,42 +106,118 @@ private void createOrUpdateSecret(final Map<String, String> data, final KafkaAcc
106106
if (kafkaAccessSecretEventSource == null) {
107107
throw new IllegalStateException("Event source for Kafka Access Secret not initialized, cannot reconcile");
108108
}
109+
110+
final Map<String, String> templateAnnotations = getTemplateAnnotations(kafkaAccess);
111+
final Map<String, String> templateLabels = getTemplateAndCommonLabels(kafkaAccess);
112+
109113
kafkaAccessSecretEventSource.get(new ResourceID(secretName, kafkaAccessNamespace))
110-
.ifPresentOrElse(secret -> {
111-
final Map<String, String> currentData = secret.getData();
112-
if (!data.equals(currentData)) {
113-
kubernetesClient.secrets()
114-
.inNamespace(kafkaAccessNamespace)
115-
.withName(secretName)
116-
.edit(s -> new SecretBuilder(s).withData(data).build());
117-
}
118-
}, () -> kubernetesClient
119-
.secrets()
120-
.inNamespace(kafkaAccessNamespace)
121-
.resource(
122-
new SecretBuilder()
123-
.withType(SECRET_TYPE)
124-
.withNewMetadata()
125-
.withName(secretName)
126-
.withLabels(commonSecretLabels)
127-
.withOwnerReferences(
128-
new OwnerReferenceBuilder()
129-
.withApiVersion(kafkaAccess.getApiVersion())
130-
.withKind(kafkaAccess.getKind())
131-
.withName(kafkaAccessName)
132-
.withUid(kafkaAccess.getMetadata().getUid())
133-
.withBlockOwnerDeletion(false)
134-
.withController(false)
135-
.build()
136-
)
137-
.endMetadata()
138-
.withData(data)
139-
.build()
140-
)
141-
.create()
114+
.ifPresentOrElse(
115+
secret -> updateSecretIfChanged(secret, data, templateAnnotations, templateLabels, kafkaAccessNamespace, secretName),
116+
() -> createSecret(data, kafkaAccess, secretName, kafkaAccessNamespace, kafkaAccessName, templateAnnotations, templateLabels)
142117
);
143118
}
144119

120+
private void updateSecretIfChanged(Secret secret, Map<String, String> data, Map<String, String> templateAnnotations,
121+
Map<String, String> templateLabels, String namespace, String secretName) {
122+
final Map<String, String> mergedAnnotations = mergeWithoutOverwritingCurrent(
123+
Optional.ofNullable(secret.getMetadata().getAnnotations()).orElse(Map.of()),
124+
templateAnnotations);
125+
126+
final Map<String, String> mergedLabels = mergeWithoutOverwritingCurrent(
127+
Optional.ofNullable(secret.getMetadata().getLabels()).orElse(Map.of()),
128+
templateLabels);
129+
130+
final boolean dataChanged = !data.equals(Optional.ofNullable(secret.getData()).orElse(Map.of()));
131+
final boolean annotationsChanged = !mergedAnnotations.equals(
132+
Optional.ofNullable(secret.getMetadata().getAnnotations()).orElse(Map.of()));
133+
final boolean labelsChanged = !mergedLabels.equals(
134+
Optional.ofNullable(secret.getMetadata().getLabels()).orElse(Map.of()));
135+
136+
if (dataChanged || annotationsChanged || labelsChanged) {
137+
kubernetesClient.secrets()
138+
.inNamespace(namespace)
139+
.withName(secretName)
140+
.edit(s -> new SecretBuilder(s)
141+
.withData(data)
142+
.editOrNewMetadata()
143+
.withAnnotations(mergedAnnotations)
144+
.withLabels(mergedLabels)
145+
.endMetadata()
146+
.build());
147+
}
148+
}
149+
150+
private static Map<String, String> mergeWithoutOverwritingCurrent(Map<String, String> current, Map<String, String> template) {
151+
Map<String, String> merged = new HashMap<>(template);
152+
merged.putAll(current);
153+
return merged;
154+
}
155+
156+
private void createSecret(Map<String, String> data, KafkaAccess kafkaAccess, String secretName, String namespace,
157+
String kafkaAccessName, Map<String, String> templateAnnotations, Map<String, String> templateLabels) {
158+
kubernetesClient
159+
.secrets()
160+
.inNamespace(namespace)
161+
.resource(
162+
new SecretBuilder()
163+
.withType(SECRET_TYPE)
164+
.withNewMetadata()
165+
.withName(secretName)
166+
.withLabels(templateLabels)
167+
.withAnnotations(templateAnnotations)
168+
.withOwnerReferences(
169+
new OwnerReferenceBuilder()
170+
.withApiVersion(kafkaAccess.getApiVersion())
171+
.withKind(kafkaAccess.getKind())
172+
.withName(kafkaAccessName)
173+
.withUid(kafkaAccess.getMetadata().getUid())
174+
.withBlockOwnerDeletion(false)
175+
.withController(false)
176+
.build()
177+
)
178+
.endMetadata()
179+
.withData(data)
180+
.build()
181+
)
182+
.create();
183+
}
184+
185+
/**
186+
* Extracts annotations from the KafkaAccess spec template that should be applied to the Secret.
187+
*
188+
* @param kafkaAccess The KafkaAccess custom resource.
189+
* @return A map of annotations to apply to the Secret.
190+
*/
191+
private Map<String, String> getTemplateAnnotations(final KafkaAccess kafkaAccess) {
192+
return new HashMap<>(Optional.ofNullable(kafkaAccess.getSpec().getTemplate())
193+
.map(template -> template.getSecret())
194+
.map(secret -> secret.getMetadata())
195+
.map(metadata -> metadata.getAnnotations())
196+
.orElse(new HashMap<>()));
197+
}
198+
199+
/**
200+
* Builds the desired Secret labels from the KafkaAccess template.
201+
* Template-provided labels are included, then operator-required common labels are added.
202+
* On key conflicts, operator-required common labels take precedence.
203+
*
204+
* @param kafkaAccess The KafkaAccess custom resource.
205+
* @return A map of labels to apply to the Secret, including operator-required common labels.
206+
*/
207+
private Map<String, String> getTemplateAndCommonLabels(final KafkaAccess kafkaAccess) {
208+
Map<String, String> labels = Optional.ofNullable(kafkaAccess.getSpec().getTemplate())
209+
.map(template -> template.getSecret())
210+
.map(secret -> secret.getMetadata())
211+
.map(metadata -> metadata.getLabels())
212+
.map(HashMap::new)
213+
.orElse(new HashMap<>());
214+
215+
// Keep operator default labels authoritative.
216+
labels.putAll(commonSecretLabels);
217+
218+
return labels;
219+
}
220+
145221
/**
146222
* Prepares the event sources required for triggering the reconciliation.
147223
* It configures the JOSDK framework with resources the operator needs to watch.

0 commit comments

Comments
 (0)