diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java index 174cf10619..912d876aba 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java @@ -88,7 +88,7 @@ private SecondaryToPrimaryMapper getSecondaryToPrimaryMapper() { return (SecondaryToPrimaryMapper) this; } else if (garbageCollected) { return Mappers.fromOwnerReference(); - } else if (useDefaultAnnotationsToIdentifyPrimary()) { + } else if (useNonOwnerRefBasedSecondaryToPrimaryMapping()) { return Mappers.fromDefaultAnnotations(); } else { throw new OperatorException("Provide a SecondaryToPrimaryMapper to associate " + @@ -238,8 +238,8 @@ protected Resource prepare(R desired, P primary, String actionName) { protected void addReferenceHandlingMetadata(R desired, P primary) { if (addOwnerReference()) { desired.addOwnerReference(primary); - } else if (useDefaultAnnotationsToIdentifyPrimary()) { - addDefaultSecondaryToPrimaryMapperAnnotations(desired, primary); + } else if (useNonOwnerRefBasedSecondaryToPrimaryMapping()) { + addSecondaryToPrimaryMapperAnnotations(desired, primary); } } @@ -269,17 +269,22 @@ protected InformerEventSource createEventSource(EventSourceContext

cont return eventSource().orElseThrow(); } - private boolean useDefaultAnnotationsToIdentifyPrimary() { - return !(this instanceof SecondaryToPrimaryMapper) && !garbageCollected && isCreatable(); + private boolean useNonOwnerRefBasedSecondaryToPrimaryMapping() { + return !garbageCollected && isCreatable(); } - private void addDefaultSecondaryToPrimaryMapperAnnotations(R desired, P primary) { + protected void addSecondaryToPrimaryMapperAnnotations(R desired, P primary) { + addSecondaryToPrimaryMapperAnnotations(desired, primary, Mappers.DEFAULT_ANNOTATION_FOR_NAME, + Mappers.DEFAULT_ANNOTATION_FOR_NAMESPACE); + } + + protected void addSecondaryToPrimaryMapperAnnotations(R desired, P primary, String nameKey, + String namespaceKey) { var annotations = desired.getMetadata().getAnnotations(); - annotations.put(Mappers.DEFAULT_ANNOTATION_FOR_NAME, primary.getMetadata().getName()); + annotations.put(nameKey, primary.getMetadata().getName()); var primaryNamespaces = primary.getMetadata().getNamespace(); if (primaryNamespaces != null) { - annotations.put( - Mappers.DEFAULT_ANNOTATION_FOR_NAMESPACE, primary.getMetadata().getNamespace()); + annotations.put(namespaceKey, primary.getMetadata().getNamespace()); } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/SecondaryToPrimaryMapper.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/SecondaryToPrimaryMapper.java index 45573542c2..7b8853b4ae 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/SecondaryToPrimaryMapper.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/SecondaryToPrimaryMapper.java @@ -6,5 +6,5 @@ @FunctionalInterface public interface SecondaryToPrimaryMapper { - Set toPrimaryResourceIDs(R dependentResource); + Set toPrimaryResourceIDs(R resource); } diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/DependentCustomMappingAnnotationIT.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/DependentCustomMappingAnnotationIT.java new file mode 100644 index 0000000000..8a7d168b26 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/DependentCustomMappingAnnotationIT.java @@ -0,0 +1,72 @@ +package io.javaoperatorsdk.operator; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension; +import io.javaoperatorsdk.operator.sample.dependentcustommappingannotation.CustomMappingConfigMapDependentResource; +import io.javaoperatorsdk.operator.sample.dependentcustommappingannotation.DependentCustomMappingCustomResource; +import io.javaoperatorsdk.operator.sample.dependentcustommappingannotation.DependentCustomMappingReconciler; +import io.javaoperatorsdk.operator.sample.dependentcustommappingannotation.DependentCustomMappingSpec; + +import static io.javaoperatorsdk.operator.sample.dependentcustommappingannotation.CustomMappingConfigMapDependentResource.CUSTOM_NAMESPACE_KEY; +import static io.javaoperatorsdk.operator.sample.dependentcustommappingannotation.CustomMappingConfigMapDependentResource.CUSTOM_NAME_KEY; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +class DependentCustomMappingAnnotationIT { + + public static final String INITIAL_VALUE = "initial value"; + public static final String CHANGED_VALUE = "changed value"; + public static final String TEST_RESOURCE_NAME = "test1"; + + @RegisterExtension + LocallyRunOperatorExtension extension = + LocallyRunOperatorExtension.builder() + .withReconciler(DependentCustomMappingReconciler.class) + .build(); + + + @Test + void testCustomMappingAnnotationForDependent() { + var cr = extension.create(testResource()); + assertConfigMapData(INITIAL_VALUE); + + cr.getSpec().setValue(CHANGED_VALUE); + cr = extension.replace(cr); + assertConfigMapData(CHANGED_VALUE); + + extension.delete(cr); + + await().untilAsserted(() -> { + var resource = extension.get(ConfigMap.class, TEST_RESOURCE_NAME); + assertThat(resource).isNull(); + }); + } + + private void assertConfigMapData(String val) { + await().untilAsserted(() -> { + var resource = extension.get(ConfigMap.class, TEST_RESOURCE_NAME); + assertThat(resource).isNotNull(); + assertThat(resource.getMetadata().getAnnotations()) + .containsKey(CUSTOM_NAME_KEY) + .containsKey(CUSTOM_NAMESPACE_KEY); + assertThat(resource.getData()).containsEntry(CustomMappingConfigMapDependentResource.KEY, + val); + }); + } + + + DependentCustomMappingCustomResource testResource() { + var dr = new DependentCustomMappingCustomResource(); + dr.setMetadata(new ObjectMetaBuilder().withName(TEST_RESOURCE_NAME).build()); + dr.setSpec(new DependentCustomMappingSpec()); + dr.getSpec().setValue(INITIAL_VALUE); + + return dr; + } + + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentcustommappingannotation/CustomMappingConfigMapDependentResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentcustommappingannotation/CustomMappingConfigMapDependentResource.java new file mode 100644 index 0000000000..123d360068 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentcustommappingannotation/CustomMappingConfigMapDependentResource.java @@ -0,0 +1,52 @@ +package io.javaoperatorsdk.operator.sample.dependentcustommappingannotation; + +import java.util.Map; +import java.util.Set; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapBuilder; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.javaoperatorsdk.operator.api.reconciler.Context; +import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDNoGCKubernetesDependentResource; +import io.javaoperatorsdk.operator.processing.event.ResourceID; +import io.javaoperatorsdk.operator.processing.event.source.SecondaryToPrimaryMapper; +import io.javaoperatorsdk.operator.processing.event.source.informer.Mappers; + +public class CustomMappingConfigMapDependentResource + extends CRUDNoGCKubernetesDependentResource + implements SecondaryToPrimaryMapper { + + public static final String CUSTOM_NAME_KEY = "customNameKey"; + public static final String CUSTOM_NAMESPACE_KEY = "customNamespaceKey"; + public static final String KEY = "key"; + + private SecondaryToPrimaryMapper mapper = + Mappers.fromAnnotation(CUSTOM_NAME_KEY, CUSTOM_NAMESPACE_KEY); + + public CustomMappingConfigMapDependentResource() { + super(ConfigMap.class); + } + + @Override + protected ConfigMap desired(DependentCustomMappingCustomResource primary, + Context context) { + return new ConfigMapBuilder() + .withMetadata(new ObjectMetaBuilder() + .withName(primary.getMetadata().getName()) + .withNamespace(primary.getMetadata().getNamespace()) + .build()) + .withData(Map.of(KEY, primary.getSpec().getValue())) + .build(); + } + + @Override + public Set toPrimaryResourceIDs(ConfigMap resource) { + return mapper.toPrimaryResourceIDs(resource); + } + + @Override + protected void addSecondaryToPrimaryMapperAnnotations(ConfigMap desired, + DependentCustomMappingCustomResource primary) { + addSecondaryToPrimaryMapperAnnotations(desired, primary, CUSTOM_NAME_KEY, CUSTOM_NAMESPACE_KEY); + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentcustommappingannotation/DependentCustomMappingCustomResource.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentcustommappingannotation/DependentCustomMappingCustomResource.java new file mode 100644 index 0000000000..47776ed1e7 --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentcustommappingannotation/DependentCustomMappingCustomResource.java @@ -0,0 +1,14 @@ +package io.javaoperatorsdk.operator.sample.dependentcustommappingannotation; + +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.javaoperatorsdk") +@Version("v1") +public class DependentCustomMappingCustomResource + extends CustomResource + implements Namespaced { + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentcustommappingannotation/DependentCustomMappingReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentcustommappingannotation/DependentCustomMappingReconciler.java new file mode 100644 index 0000000000..8c14f829ff --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentcustommappingannotation/DependentCustomMappingReconciler.java @@ -0,0 +1,20 @@ +package io.javaoperatorsdk.operator.sample.dependentcustommappingannotation; + +import io.javaoperatorsdk.operator.api.reconciler.*; +import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent; + +@ControllerConfiguration( + dependents = {@Dependent(type = CustomMappingConfigMapDependentResource.class)}) +public class DependentCustomMappingReconciler + implements Reconciler { + + @Override + public UpdateControl reconcile( + DependentCustomMappingCustomResource resource, + Context context) throws Exception { + + return UpdateControl.noUpdate(); + } + + +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentcustommappingannotation/DependentCustomMappingSpec.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentcustommappingannotation/DependentCustomMappingSpec.java new file mode 100644 index 0000000000..bc5b92901f --- /dev/null +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/dependentcustommappingannotation/DependentCustomMappingSpec.java @@ -0,0 +1,15 @@ +package io.javaoperatorsdk.operator.sample.dependentcustommappingannotation; + +public class DependentCustomMappingSpec { + + private String value; + + public String getValue() { + return value; + } + + public DependentCustomMappingSpec setValue(String value) { + this.value = value; + return this; + } +} diff --git a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primaryindexer/DependentPrimaryIndexerTestReconciler.java b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primaryindexer/DependentPrimaryIndexerTestReconciler.java index e123500c42..89b2a43700 100644 --- a/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primaryindexer/DependentPrimaryIndexerTestReconciler.java +++ b/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/primaryindexer/DependentPrimaryIndexerTestReconciler.java @@ -31,8 +31,8 @@ public ReadOnlyConfigMapDependent() { } @Override - public Set toPrimaryResourceIDs(ConfigMap dependentResource) { - return cache.byIndex(CONFIG_MAP_RELATION_INDEXER, dependentResource.getMetadata().getName()) + public Set toPrimaryResourceIDs(ConfigMap resource) { + return cache.byIndex(CONFIG_MAP_RELATION_INDEXER, resource.getMetadata().getName()) .stream() .map(ResourceID::fromResource) .collect(Collectors.toSet()); diff --git a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SecretDependentResource.java b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SecretDependentResource.java index 1aa2ad62e5..edaa5707ee 100644 --- a/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SecretDependentResource.java +++ b/sample-operators/mysql-schema/src/main/java/io/javaoperatorsdk/operator/sample/dependent/SecretDependentResource.java @@ -61,9 +61,9 @@ public Result match(Secret actual, MySQLSchema primary, Context toPrimaryResourceIDs(Secret dependentResource) { - String name = dependentResource.getMetadata().getName(); + public Set toPrimaryResourceIDs(Secret resource) { + String name = resource.getMetadata().getName(); return Set.of(new ResourceID(name.substring(0, name.length() - SECRET_SUFFIX.length()), - dependentResource.getMetadata().getNamespace())); + resource.getMetadata().getNamespace())); } }