Skip to content

Commit 2832fc2

Browse files
authored
feature: update preserves metadata (#960)
1 parent 569c079 commit 2832fc2

File tree

5 files changed

+119
-7
lines changed

5 files changed

+119
-7
lines changed

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/ReconcilerUtils.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,15 @@ public static Object getSpec(HasMetadata resource) {
118118
}
119119
}
120120

121+
public static Object setSpec(HasMetadata resource, Object spec) {
122+
try {
123+
Method setSpecMethod = resource.getClass().getMethod("setSpec", spec.getClass());
124+
return setSpecMethod.invoke(resource, spec);
125+
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
126+
throw new IllegalStateException("No spec found on resource", e);
127+
}
128+
}
129+
121130
public static <T> T loadYaml(Class<T> clazz, Class loader, String yaml) {
122131
try (InputStream is = loader.getResourceAsStream(yaml)) {
123132
return Serialization.unmarshal(is, clazz);

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public abstract class KubernetesDependentResource<R extends HasMetadata, P exten
3535
private InformerEventSource<R, P> informerEventSource;
3636
private boolean addOwnerReference;
3737
protected ResourceMatcher resourceMatcher;
38+
protected ResourceUpdatePreProcessor<R> resourceUpdatePreProcessor;
3839

3940
@Override
4041
public void configureWith(KubernetesDependentResourceConfig config) {
@@ -74,10 +75,10 @@ public void configureWith(ConfigurationService configurationService,
7475
boolean addOwnerReference) {
7576
this.informerEventSource = informerEventSource;
7677
this.addOwnerReference = addOwnerReference;
77-
initResourceMatcherIfNotSet(configurationService);
78+
initResourceMatcherAndUpdatePreProcessorIfNotSet(configurationService);
7879
}
7980

80-
protected void beforeCreateOrUpdate(R desired, P primary) {
81+
protected void beforeCreate(R desired, P primary) {
8182
if (addOwnerReference) {
8283
desired.addOwnerReference(primary);
8384
}
@@ -93,7 +94,7 @@ protected boolean match(R actualResource, R desiredResource, Context context) {
9394
protected R create(R target, P primary, Context context) {
9495
log.debug("Creating target resource with type: " +
9596
"{}, with id: {}", target.getClass(), ResourceID.fromResource(target));
96-
beforeCreateOrUpdate(target, primary);
97+
beforeCreate(target, primary);
9798
Class<R> targetClass = (Class<R>) target.getClass();
9899
return client.resources(targetClass).inNamespace(target.getMetadata().getNamespace())
99100
.create(target);
@@ -104,15 +105,15 @@ protected R create(R target, P primary, Context context) {
104105
protected R update(R actual, R target, P primary, Context context) {
105106
log.debug("Updating target resource with type: {}, with id: {}", target.getClass(),
106107
ResourceID.fromResource(target));
107-
beforeCreateOrUpdate(target, primary);
108108
Class<R> targetClass = (Class<R>) target.getClass();
109+
var updatedActual = resourceUpdatePreProcessor.replaceSpecOnActual(actual, target);
109110
return client.resources(targetClass).inNamespace(target.getMetadata().getNamespace())
110-
.replace(target);
111+
.replace(updatedActual);
111112
}
112113

113114
@Override
114115
public EventSource eventSource(EventSourceContext<P> context) {
115-
initResourceMatcherIfNotSet(context.getConfigurationService());
116+
initResourceMatcherAndUpdatePreProcessorIfNotSet(context.getConfigurationService());
116117
if (informerEventSource == null) {
117118
configureWith(context.getConfigurationService(), null, null,
118119
KubernetesDependent.ADD_OWNER_REFERENCE_DEFAULT);
@@ -156,10 +157,25 @@ public void setKubernetesClient(KubernetesClient kubernetesClient) {
156157
*
157158
* @param configurationService config service to mainly access object mapper
158159
*/
159-
protected void initResourceMatcherIfNotSet(ConfigurationService configurationService) {
160+
protected void initResourceMatcherAndUpdatePreProcessorIfNotSet(
161+
ConfigurationService configurationService) {
160162
if (resourceMatcher == null) {
161163
resourceMatcher = new DesiredValueMatcher(configurationService.getObjectMapper());
162164
}
165+
if (resourceUpdatePreProcessor == null) {
166+
resourceUpdatePreProcessor =
167+
new ResourceUpdatePreProcessor<>(configurationService.getResourceCloner());
168+
}
169+
}
170+
171+
public KubernetesDependentResource<R, P> setResourceMatcher(ResourceMatcher resourceMatcher) {
172+
this.resourceMatcher = resourceMatcher;
173+
return this;
163174
}
164175

176+
public KubernetesDependentResource<R, P> setResourceUpdatePreProcessor(
177+
ResourceUpdatePreProcessor<R> resourceUpdatePreProcessor) {
178+
this.resourceUpdatePreProcessor = resourceUpdatePreProcessor;
179+
return this;
180+
}
165181
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.javaoperatorsdk.operator.processing.dependent.kubernetes;
2+
3+
import io.fabric8.kubernetes.api.model.ConfigMap;
4+
import io.fabric8.kubernetes.api.model.HasMetadata;
5+
import io.fabric8.kubernetes.api.model.Secret;
6+
import io.javaoperatorsdk.operator.ReconcilerUtils;
7+
import io.javaoperatorsdk.operator.api.config.Cloner;
8+
9+
public class ResourceUpdatePreProcessor<R extends HasMetadata> {
10+
11+
private final Cloner cloner;
12+
13+
public ResourceUpdatePreProcessor(Cloner cloner) {
14+
this.cloner = cloner;
15+
}
16+
17+
public R replaceSpecOnActual(R actual, R desired) {
18+
var clonedActual = cloner.clone(actual);
19+
if (desired instanceof ConfigMap) {
20+
((ConfigMap) clonedActual).setData(((ConfigMap) desired).getData());
21+
((ConfigMap) clonedActual).setBinaryData((((ConfigMap) desired).getBinaryData()));
22+
return clonedActual;
23+
} else if (desired instanceof Secret) {
24+
((Secret) clonedActual).setData(((Secret) desired).getData());
25+
((Secret) clonedActual).setStringData(((Secret) desired).getStringData());
26+
return clonedActual;
27+
} else {
28+
var desiredSpec = ReconcilerUtils.getSpec(desired);
29+
ReconcilerUtils.setSpec(clonedActual, desiredSpec);
30+
return clonedActual;
31+
}
32+
}
33+
}

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/ReconcilerUtilsTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,19 @@ void getsSpecWithReflection() {
7272
assertThat(spec.getReplicas()).isEqualTo(5);
7373
}
7474

75+
@Test
76+
void setsSpecWithReflection() {
77+
Deployment deployment = new Deployment();
78+
deployment.setSpec(new DeploymentSpec());
79+
deployment.getSpec().setReplicas(5);
80+
DeploymentSpec newSpec = new DeploymentSpec();
81+
newSpec.setReplicas(1);
82+
83+
ReconcilerUtils.setSpec(deployment, newSpec);
84+
85+
assertThat(deployment.getSpec().getReplicas()).isEqualTo(1);
86+
}
87+
7588
private Deployment createTestDeployment() {
7689
Deployment deployment = new Deployment();
7790
deployment.setSpec(new DeploymentSpec());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package io.javaoperatorsdk.operator.processing.dependent.kubernetes;
2+
3+
import java.util.HashMap;
4+
5+
import org.junit.jupiter.api.Test;
6+
7+
import io.fabric8.kubernetes.api.model.apps.Deployment;
8+
import io.javaoperatorsdk.operator.ReconcilerUtils;
9+
import io.javaoperatorsdk.operator.api.config.ConfigurationService;
10+
11+
import static org.assertj.core.api.Assertions.assertThat;
12+
13+
class ResourceUpdatePreProcessorTest {
14+
15+
ResourceUpdatePreProcessor<Deployment> resourceUpdatePreProcessor =
16+
new ResourceUpdatePreProcessor<>(ConfigurationService.DEFAULT_CLONER);
17+
18+
@Test
19+
void preservesValues() {
20+
var desired = createDeployment();
21+
var actual = createDeployment();
22+
actual.getMetadata().setLabels(new HashMap<>());
23+
actual.getMetadata().getLabels().put("additionalActualKey", "value");
24+
actual.getMetadata().setResourceVersion("1234");
25+
actual.getSpec().setRevisionHistoryLimit(5);
26+
27+
var result = resourceUpdatePreProcessor.replaceSpecOnActual(actual, desired);
28+
29+
assertThat(result.getMetadata().getLabels().get("additionalActualKey")).isEqualTo("value");
30+
assertThat(result.getMetadata().getResourceVersion()).isEqualTo("1234");
31+
assertThat(result.getSpec().getRevisionHistoryLimit()).isEqualTo(10);
32+
}
33+
34+
Deployment createDeployment() {
35+
Deployment deployment =
36+
ReconcilerUtils.loadYaml(
37+
Deployment.class, ResourceUpdatePreProcessorTest.class, "nginx-deployment.yaml");
38+
return deployment;
39+
}
40+
41+
}

0 commit comments

Comments
 (0)