Skip to content

Commit d5a5737

Browse files
committed
feat: create resource only if not exists (#2001)
1 parent 8234e84 commit d5a5737

File tree

8 files changed

+169
-8
lines changed

8 files changed

+169
-8
lines changed

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

+8-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77

88
import io.javaoperatorsdk.operator.api.reconciler.Constants;
99
import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator;
10-
import io.javaoperatorsdk.operator.processing.event.source.filter.*;
10+
import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
11+
import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
12+
import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter;
13+
import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
1114

1215
import static io.javaoperatorsdk.operator.api.reconciler.Constants.NO_VALUE_SET;
1316

@@ -69,4 +72,8 @@
6972

7073
Class<? extends ResourceDiscriminator> resourceDiscriminator() default ResourceDiscriminator.class;
7174

75+
/**
76+
* Creates the resource only if did not exist before, this applies only if SSA is used.
77+
*/
78+
boolean createResourceOnlyIfNotExistingWithSSA() default KubernetesDependentResourceConfig.DEFAULT_CREATE_RESOURCE_ONLY_IF_NOT_EXISTING_WITH_SSA;
7279
}

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

+9-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter;
1515
import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;
1616

17+
import static io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig.DEFAULT_CREATE_RESOURCE_ONLY_IF_NOT_EXISTING_WITH_SSA;
18+
1719
public class KubernetesDependentConverter<R extends HasMetadata, P extends HasMetadata> implements
1820
ConfigurationConverter<KubernetesDependent, KubernetesDependentResourceConfig<R>, KubernetesDependentResource<R, P>> {
1921

@@ -25,6 +27,8 @@ public KubernetesDependentResourceConfig<R> configFrom(KubernetesDependent confi
2527
var namespaces = parentConfiguration.getNamespaces();
2628
var configuredNS = false;
2729
String labelSelector = null;
30+
var createResourceOnlyIfNotExistingWithSSA =
31+
DEFAULT_CREATE_RESOURCE_ONLY_IF_NOT_EXISTING_WITH_SSA;
2832
OnAddFilter<? extends HasMetadata> onAddFilter = null;
2933
OnUpdateFilter<? extends HasMetadata> onUpdateFilter = null;
3034
OnDeleteFilter<? extends HasMetadata> onDeleteFilter = null;
@@ -39,9 +43,8 @@ public KubernetesDependentResourceConfig<R> configFrom(KubernetesDependent confi
3943
final var fromAnnotation = configAnnotation.labelSelector();
4044
labelSelector = Constants.NO_VALUE_SET.equals(fromAnnotation) ? null : fromAnnotation;
4145

42-
final var context =
43-
Utils.contextFor(parentConfiguration, originatingClass,
44-
configAnnotation.annotationType());
46+
final var context = Utils.contextFor(parentConfiguration, originatingClass,
47+
configAnnotation.annotationType());
4548
onAddFilter = Utils.instantiate(configAnnotation.onAddFilter(), OnAddFilter.class, context);
4649
onUpdateFilter =
4750
Utils.instantiate(configAnnotation.onUpdateFilter(), OnUpdateFilter.class, context);
@@ -53,9 +56,12 @@ public KubernetesDependentResourceConfig<R> configFrom(KubernetesDependent confi
5356
resourceDiscriminator =
5457
Utils.instantiate(configAnnotation.resourceDiscriminator(), ResourceDiscriminator.class,
5558
context);
59+
createResourceOnlyIfNotExistingWithSSA =
60+
configAnnotation.createResourceOnlyIfNotExistingWithSSA();
5661
}
5762

5863
return new KubernetesDependentResourceConfig(namespaces, labelSelector, configuredNS,
64+
createResourceOnlyIfNotExistingWithSSA,
5965
resourceDiscriminator, onAddFilter, onUpdateFilter, onDeleteFilter, genericFilter);
6066
}
6167
}

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,12 @@ protected R handleUpdate(R actual, R desired, P primary, Context<P> context) {
130130
public R create(R target, P primary, Context<P> context) {
131131
if (useSSA(context)) {
132132
// setting resource version for SSA so only created if it doesn't exist already
133-
target.getMetadata().setResourceVersion("1");
133+
var createIfNotExisting = kubernetesDependentResourceConfig == null
134+
? KubernetesDependentResourceConfig.DEFAULT_CREATE_RESOURCE_ONLY_IF_NOT_EXISTING_WITH_SSA
135+
: kubernetesDependentResourceConfig.createResourceOnlyIfNotExistingWithSSA();
136+
if (createIfNotExisting) {
137+
target.getMetadata().setResourceVersion("1");
138+
}
134139
}
135140
final var resource = prepare(target, primary, "Creating");
136141
return useSSA(context)

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

+14-3
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,12 @@
1313

1414
public class KubernetesDependentResourceConfig<R> {
1515

16+
public static final boolean DEFAULT_CREATE_RESOURCE_ONLY_IF_NOT_EXISTING_WITH_SSA = true;
17+
1618
private Set<String> namespaces = Constants.SAME_AS_CONTROLLER_NAMESPACES_SET;
1719
private String labelSelector = NO_VALUE_SET;
1820
private boolean namespacesWereConfigured = false;
21+
private boolean createResourceOnlyIfNotExistingWithSSA;
1922
private ResourceDiscriminator<R, ?> resourceDiscriminator;
2023

2124
private OnAddFilter<R> onAddFilter;
@@ -28,14 +31,18 @@ public class KubernetesDependentResourceConfig<R> {
2831

2932
public KubernetesDependentResourceConfig() {}
3033

31-
public KubernetesDependentResourceConfig(Set<String> namespaces, String labelSelector,
32-
boolean configuredNS, ResourceDiscriminator<R, ?> resourceDiscriminator,
34+
public KubernetesDependentResourceConfig(Set<String> namespaces,
35+
String labelSelector,
36+
boolean configuredNS,
37+
boolean createResourceOnlyIfNotExistingWithSSA,
38+
ResourceDiscriminator<R, ?> resourceDiscriminator,
3339
OnAddFilter<R> onAddFilter,
3440
OnUpdateFilter<R> onUpdateFilter,
3541
OnDeleteFilter<R> onDeleteFilter, GenericFilter<R> genericFilter) {
3642
this.namespaces = namespaces;
3743
this.labelSelector = labelSelector;
3844
this.namespacesWereConfigured = configuredNS;
45+
this.createResourceOnlyIfNotExistingWithSSA = createResourceOnlyIfNotExistingWithSSA;
3946
this.onAddFilter = onAddFilter;
4047
this.onUpdateFilter = onUpdateFilter;
4148
this.onDeleteFilter = onDeleteFilter;
@@ -44,7 +51,8 @@ public KubernetesDependentResourceConfig(Set<String> namespaces, String labelSel
4451
}
4552

4653
public KubernetesDependentResourceConfig(Set<String> namespaces, String labelSelector) {
47-
this(namespaces, labelSelector, true, null, null, null,
54+
this(namespaces, labelSelector, true, DEFAULT_CREATE_RESOURCE_ONLY_IF_NOT_EXISTING_WITH_SSA,
55+
null, null, null,
4856
null, null);
4957
}
5058

@@ -70,6 +78,9 @@ public OnAddFilter onAddFilter() {
7078
return onAddFilter;
7179
}
7280

81+
public boolean createResourceOnlyIfNotExistingWithSSA() {
82+
return createResourceOnlyIfNotExistingWithSSA;
83+
}
7384

7485
public OnUpdateFilter<R> onUpdateFilter() {
7586
return onUpdateFilter;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package io.javaoperatorsdk.operator;
2+
3+
import java.time.Duration;
4+
import java.util.Map;
5+
6+
import org.junit.jupiter.api.Test;
7+
import org.junit.jupiter.api.extension.RegisterExtension;
8+
9+
import io.fabric8.kubernetes.api.model.ConfigMap;
10+
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
11+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
12+
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
13+
import io.javaoperatorsdk.operator.sample.createonlyifnotexistsdependentwithssa.CreateOnlyIfNotExistingDependentWithSSACustomResource;
14+
import io.javaoperatorsdk.operator.sample.createonlyifnotexistsdependentwithssa.CreateOnlyIfNotExistingDependentWithSSAReconciler;
15+
16+
import static org.assertj.core.api.Assertions.assertThat;
17+
import static org.awaitility.Awaitility.await;
18+
19+
class CreateOnlyIfNotExistingDependentWithSSA {
20+
21+
public static final String TEST_RESOURCE_NAME = "test1";
22+
public static final String KEY = "key";
23+
24+
@RegisterExtension
25+
LocallyRunOperatorExtension extension =
26+
LocallyRunOperatorExtension.builder()
27+
.withReconciler(new CreateOnlyIfNotExistingDependentWithSSAReconciler())
28+
.build();
29+
30+
31+
@Test
32+
void createsResourceOnlyIfNotExisting() {
33+
var cm = new ConfigMapBuilder().withMetadata(new ObjectMetaBuilder()
34+
.withName(TEST_RESOURCE_NAME)
35+
.build())
36+
.withData(Map.of(KEY, "val"))
37+
.build();
38+
39+
extension.create(cm);
40+
extension.create(testResource());
41+
42+
await().pollDelay(Duration.ofMillis(200)).untilAsserted(() -> {
43+
var currentCM = extension.get(ConfigMap.class, TEST_RESOURCE_NAME);
44+
assertThat(currentCM.getData()).containsKey(KEY);
45+
});
46+
}
47+
48+
CreateOnlyIfNotExistingDependentWithSSACustomResource testResource() {
49+
var res = new CreateOnlyIfNotExistingDependentWithSSACustomResource();
50+
res.setMetadata(new ObjectMetaBuilder()
51+
.withName(TEST_RESOURCE_NAME)
52+
.build());
53+
54+
return res;
55+
}
56+
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.javaoperatorsdk.operator.sample.createonlyifnotexistsdependentwithssa;
2+
3+
import java.util.Map;
4+
5+
import io.fabric8.kubernetes.api.model.ConfigMap;
6+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
7+
import io.javaoperatorsdk.operator.api.reconciler.Context;
8+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
9+
10+
public class ConfigMapDependentResource extends
11+
CRUDKubernetesDependentResource<ConfigMap, CreateOnlyIfNotExistingDependentWithSSACustomResource> {
12+
13+
public ConfigMapDependentResource() {
14+
super(ConfigMap.class);
15+
}
16+
17+
@Override
18+
protected ConfigMap desired(CreateOnlyIfNotExistingDependentWithSSACustomResource primary,
19+
Context<CreateOnlyIfNotExistingDependentWithSSACustomResource> context) {
20+
ConfigMap configMap = new ConfigMap();
21+
configMap.setMetadata(new ObjectMetaBuilder()
22+
.withName(primary.getMetadata().getName())
23+
.withNamespace(primary.getMetadata().getNamespace())
24+
.build());
25+
configMap.setData(Map.of("drkey", "v"));
26+
return configMap;
27+
}
28+
}
29+
30+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.javaoperatorsdk.operator.sample.createonlyifnotexistsdependentwithssa;
2+
3+
import io.fabric8.kubernetes.api.model.Namespaced;
4+
import io.fabric8.kubernetes.client.CustomResource;
5+
import io.fabric8.kubernetes.model.annotation.Group;
6+
import io.fabric8.kubernetes.model.annotation.Version;
7+
8+
@Group("sample.javaoperatorsdk")
9+
@Version("v1")
10+
public class CreateOnlyIfNotExistingDependentWithSSACustomResource
11+
extends CustomResource<Void, Void>
12+
implements Namespaced {
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package io.javaoperatorsdk.operator.sample.createonlyifnotexistsdependentwithssa;
2+
3+
import java.util.concurrent.atomic.AtomicInteger;
4+
5+
import io.javaoperatorsdk.operator.api.reconciler.Context;
6+
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
7+
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
8+
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
9+
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
10+
11+
@ControllerConfiguration(dependents = {
12+
@Dependent(type = ConfigMapDependentResource.class)})
13+
public class CreateOnlyIfNotExistingDependentWithSSAReconciler
14+
implements Reconciler<CreateOnlyIfNotExistingDependentWithSSACustomResource> {
15+
16+
private final AtomicInteger numberOfExecutions = new AtomicInteger(0);
17+
18+
@Override
19+
public UpdateControl<CreateOnlyIfNotExistingDependentWithSSACustomResource> reconcile(
20+
CreateOnlyIfNotExistingDependentWithSSACustomResource resource,
21+
Context<CreateOnlyIfNotExistingDependentWithSSACustomResource> context) {
22+
numberOfExecutions.addAndGet(1);
23+
return UpdateControl.noUpdate();
24+
}
25+
26+
public int getNumberOfExecutions() {
27+
return numberOfExecutions.get();
28+
}
29+
30+
31+
32+
}

0 commit comments

Comments
 (0)