From 82cd0176fb498a2d9662a1e50703d8d3a87ceb95 Mon Sep 17 00:00:00 2001 From: csviri Date: Mon, 21 Feb 2022 12:29:45 +0100 Subject: [PATCH] feature: update preserves metadata --- .../operator/ReconcilerUtils.java | 9 ++++ .../KubernetesDependentResource.java | 30 ++++++++++---- .../ResourceUpdatePreProcessor.java | 33 +++++++++++++++ .../operator/ReconcilerUtilsTest.java | 13 ++++++ .../ResourceUpdatePreProcessorTest.java | 41 +++++++++++++++++++ 5 files changed, 119 insertions(+), 7 deletions(-) create mode 100644 operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/ResourceUpdatePreProcessor.java create mode 100644 operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/ResourceUpdatePreProcessorTest.java diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java index 491910d13e..98a43492f9 100644 --- a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java @@ -118,6 +118,15 @@ public static Object getSpec(HasMetadata resource) { } } + public static Object setSpec(HasMetadata resource, Object spec) { + try { + Method setSpecMethod = resource.getClass().getMethod("setSpec", spec.getClass()); + return setSpecMethod.invoke(resource, spec); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + throw new IllegalStateException("No spec found on resource", e); + } + } + public static T loadYaml(Class clazz, Class loader, String yaml) { try (InputStream is = loader.getResourceAsStream(yaml)) { return Serialization.unmarshal(is, clazz); 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 182ff9b361..1204a3b844 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 @@ -35,6 +35,7 @@ public abstract class KubernetesDependentResource informerEventSource; private boolean addOwnerReference; protected ResourceMatcher resourceMatcher; + protected ResourceUpdatePreProcessor resourceUpdatePreProcessor; @Override public void configureWith(KubernetesDependentResourceConfig config) { @@ -74,10 +75,10 @@ public void configureWith(ConfigurationService configurationService, boolean addOwnerReference) { this.informerEventSource = informerEventSource; this.addOwnerReference = addOwnerReference; - initResourceMatcherIfNotSet(configurationService); + initResourceMatcherAndUpdatePreProcessorIfNotSet(configurationService); } - protected void beforeCreateOrUpdate(R desired, P primary) { + protected void beforeCreate(R desired, P primary) { if (addOwnerReference) { desired.addOwnerReference(primary); } @@ -93,7 +94,7 @@ protected boolean match(R actualResource, R desiredResource, Context context) { protected R create(R target, P primary, Context context) { log.debug("Creating target resource with type: " + "{}, with id: {}", target.getClass(), ResourceID.fromResource(target)); - beforeCreateOrUpdate(target, primary); + beforeCreate(target, primary); Class targetClass = (Class) target.getClass(); return client.resources(targetClass).inNamespace(target.getMetadata().getNamespace()) .create(target); @@ -104,15 +105,15 @@ protected R create(R target, P primary, Context context) { protected R update(R actual, R target, P primary, Context context) { log.debug("Updating target resource with type: {}, with id: {}", target.getClass(), ResourceID.fromResource(target)); - beforeCreateOrUpdate(target, primary); Class targetClass = (Class) target.getClass(); + var updatedActual = resourceUpdatePreProcessor.replaceSpecOnActual(actual, target); return client.resources(targetClass).inNamespace(target.getMetadata().getNamespace()) - .replace(target); + .replace(updatedActual); } @Override public EventSource eventSource(EventSourceContext

context) { - initResourceMatcherIfNotSet(context.getConfigurationService()); + initResourceMatcherAndUpdatePreProcessorIfNotSet(context.getConfigurationService()); if (informerEventSource == null) { configureWith(context.getConfigurationService(), null, null, KubernetesDependent.ADD_OWNER_REFERENCE_DEFAULT); @@ -156,10 +157,25 @@ public void setKubernetesClient(KubernetesClient kubernetesClient) { * * @param configurationService config service to mainly access object mapper */ - protected void initResourceMatcherIfNotSet(ConfigurationService configurationService) { + protected void initResourceMatcherAndUpdatePreProcessorIfNotSet( + ConfigurationService configurationService) { if (resourceMatcher == null) { resourceMatcher = new DesiredValueMatcher(configurationService.getObjectMapper()); } + if (resourceUpdatePreProcessor == null) { + resourceUpdatePreProcessor = + new ResourceUpdatePreProcessor<>(configurationService.getResourceCloner()); + } + } + + public KubernetesDependentResource setResourceMatcher(ResourceMatcher resourceMatcher) { + this.resourceMatcher = resourceMatcher; + return this; } + public KubernetesDependentResource setResourceUpdatePreProcessor( + ResourceUpdatePreProcessor resourceUpdatePreProcessor) { + this.resourceUpdatePreProcessor = resourceUpdatePreProcessor; + return this; + } } diff --git a/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/ResourceUpdatePreProcessor.java b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/ResourceUpdatePreProcessor.java new file mode 100644 index 0000000000..c7a16381a9 --- /dev/null +++ b/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/ResourceUpdatePreProcessor.java @@ -0,0 +1,33 @@ +package io.javaoperatorsdk.operator.processing.dependent.kubernetes; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.Secret; +import io.javaoperatorsdk.operator.ReconcilerUtils; +import io.javaoperatorsdk.operator.api.config.Cloner; + +public class ResourceUpdatePreProcessor { + + private final Cloner cloner; + + public ResourceUpdatePreProcessor(Cloner cloner) { + this.cloner = cloner; + } + + public R replaceSpecOnActual(R actual, R desired) { + var clonedActual = cloner.clone(actual); + if (desired instanceof ConfigMap) { + ((ConfigMap) clonedActual).setData(((ConfigMap) desired).getData()); + ((ConfigMap) clonedActual).setBinaryData((((ConfigMap) desired).getBinaryData())); + return clonedActual; + } else if (desired instanceof Secret) { + ((Secret) clonedActual).setData(((Secret) desired).getData()); + ((Secret) clonedActual).setStringData(((Secret) desired).getStringData()); + return clonedActual; + } else { + var desiredSpec = ReconcilerUtils.getSpec(desired); + ReconcilerUtils.setSpec(clonedActual, desiredSpec); + return clonedActual; + } + } +} diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ReconcilerUtilsTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ReconcilerUtilsTest.java index be82caa36b..e137a2ff11 100644 --- a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ReconcilerUtilsTest.java +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ReconcilerUtilsTest.java @@ -72,6 +72,19 @@ void getsSpecWithReflection() { assertThat(spec.getReplicas()).isEqualTo(5); } + @Test + void setsSpecWithReflection() { + Deployment deployment = new Deployment(); + deployment.setSpec(new DeploymentSpec()); + deployment.getSpec().setReplicas(5); + DeploymentSpec newSpec = new DeploymentSpec(); + newSpec.setReplicas(1); + + ReconcilerUtils.setSpec(deployment, newSpec); + + assertThat(deployment.getSpec().getReplicas()).isEqualTo(1); + } + private Deployment createTestDeployment() { Deployment deployment = new Deployment(); deployment.setSpec(new DeploymentSpec()); diff --git a/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/ResourceUpdatePreProcessorTest.java b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/ResourceUpdatePreProcessorTest.java new file mode 100644 index 0000000000..a4907f13b7 --- /dev/null +++ b/operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/ResourceUpdatePreProcessorTest.java @@ -0,0 +1,41 @@ +package io.javaoperatorsdk.operator.processing.dependent.kubernetes; + +import java.util.HashMap; + +import org.junit.jupiter.api.Test; + +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.javaoperatorsdk.operator.ReconcilerUtils; +import io.javaoperatorsdk.operator.api.config.ConfigurationService; + +import static org.assertj.core.api.Assertions.assertThat; + +class ResourceUpdatePreProcessorTest { + + ResourceUpdatePreProcessor resourceUpdatePreProcessor = + new ResourceUpdatePreProcessor<>(ConfigurationService.DEFAULT_CLONER); + + @Test + void preservesValues() { + var desired = createDeployment(); + var actual = createDeployment(); + actual.getMetadata().setLabels(new HashMap<>()); + actual.getMetadata().getLabels().put("additionalActualKey", "value"); + actual.getMetadata().setResourceVersion("1234"); + actual.getSpec().setRevisionHistoryLimit(5); + + var result = resourceUpdatePreProcessor.replaceSpecOnActual(actual, desired); + + assertThat(result.getMetadata().getLabels().get("additionalActualKey")).isEqualTo("value"); + assertThat(result.getMetadata().getResourceVersion()).isEqualTo("1234"); + assertThat(result.getSpec().getRevisionHistoryLimit()).isEqualTo(10); + } + + Deployment createDeployment() { + Deployment deployment = + ReconcilerUtils.loadYaml( + Deployment.class, ResourceUpdatePreProcessorTest.class, "nginx-deployment.yaml"); + return deployment; + } + +}