@@ -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