Skip to content

feat: customize mapping annotation in kubernetes dependent resource #2075

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Oct 4, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ private SecondaryToPrimaryMapper<R> getSecondaryToPrimaryMapper() {
return (SecondaryToPrimaryMapper<R>) 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 " +
Expand Down Expand Up @@ -238,8 +238,8 @@ protected Resource<R> 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);
}
}

Expand Down Expand Up @@ -269,17 +269,22 @@ protected InformerEventSource<R, P> createEventSource(EventSourceContext<P> cont
return eventSource().orElseThrow();
}

private boolean useDefaultAnnotationsToIdentifyPrimary() {
return !(this instanceof SecondaryToPrimaryMapper) && !garbageCollected && isCreatable();
private boolean useNonOwnerRefBasedSecondaryToPrimaryMapping() {
return !garbageCollected && isCreatable();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was the check on SecondToPrimaryMapper implementation removed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Made an improvement on naming.)

So it has now different definition a little, it is not just for the default annotation. Before it was using default annotation and that was checked. Now it check if it does not use owner reference for that.

}

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());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@

@FunctionalInterface
public interface SecondaryToPrimaryMapper<R> {
Set<ResourceID> toPrimaryResourceIDs(R dependentResource);
Set<ResourceID> toPrimaryResourceIDs(R resource);
}
Original file line number Diff line number Diff line change
@@ -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;
}


}
Original file line number Diff line number Diff line change
@@ -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<ConfigMap, DependentCustomMappingCustomResource>
implements SecondaryToPrimaryMapper<ConfigMap> {

public static final String CUSTOM_NAME_KEY = "customNameKey";
public static final String CUSTOM_NAMESPACE_KEY = "customNamespaceKey";
public static final String KEY = "key";

private SecondaryToPrimaryMapper<ConfigMap> mapper =
Mappers.fromAnnotation(CUSTOM_NAME_KEY, CUSTOM_NAMESPACE_KEY);

public CustomMappingConfigMapDependentResource() {
super(ConfigMap.class);
}

@Override
protected ConfigMap desired(DependentCustomMappingCustomResource primary,
Context<DependentCustomMappingCustomResource> 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<ResourceID> toPrimaryResourceIDs(ConfigMap resource) {
return mapper.toPrimaryResourceIDs(resource);
}

@Override
protected void addSecondaryToPrimaryMapperAnnotations(ConfigMap desired,
DependentCustomMappingCustomResource primary) {
addSecondaryToPrimaryMapperAnnotations(desired, primary, CUSTOM_NAME_KEY, CUSTOM_NAMESPACE_KEY);
}
}
Original file line number Diff line number Diff line change
@@ -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<DependentCustomMappingSpec, Void>
implements Namespaced {

}
Original file line number Diff line number Diff line change
@@ -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<DependentCustomMappingCustomResource> {

@Override
public UpdateControl<DependentCustomMappingCustomResource> reconcile(
DependentCustomMappingCustomResource resource,
Context<DependentCustomMappingCustomResource> context) throws Exception {

return UpdateControl.noUpdate();
}


}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ public ReadOnlyConfigMapDependent() {
}

@Override
public Set<ResourceID> toPrimaryResourceIDs(ConfigMap dependentResource) {
return cache.byIndex(CONFIG_MAP_RELATION_INDEXER, dependentResource.getMetadata().getName())
public Set<ResourceID> toPrimaryResourceIDs(ConfigMap resource) {
return cache.byIndex(CONFIG_MAP_RELATION_INDEXER, resource.getMetadata().getName())
.stream()
.map(ResourceID::fromResource)
.collect(Collectors.toSet());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ public Result<Secret> match(Secret actual, MySQLSchema primary, Context<MySQLSch
}

@Override
public Set<ResourceID> toPrimaryResourceIDs(Secret dependentResource) {
String name = dependentResource.getMetadata().getName();
public Set<ResourceID> 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()));
}
}