Skip to content

Commit

Permalink
feat: create resource only if not exists (#2001)
Browse files Browse the repository at this point in the history
Signed-off-by: csviri <[email protected]>
Signed-off-by: Attila Mészáros <[email protected]>
  • Loading branch information
csviri authored and metacosm committed Sep 15, 2023
1 parent 8af00f2 commit 0d22160
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@

import io.javaoperatorsdk.operator.api.reconciler.Constants;
import io.javaoperatorsdk.operator.api.reconciler.ResourceDiscriminator;
import io.javaoperatorsdk.operator.processing.event.source.filter.*;
import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;

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

Expand Down Expand Up @@ -69,4 +72,8 @@

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

/**
* Creates the resource only if did not exist before, this applies only if SSA is used.
*/
boolean createResourceOnlyIfNotExistingWithSSA() default KubernetesDependentResourceConfig.DEFAULT_CREATE_RESOURCE_ONLY_IF_NOT_EXISTING_WITH_SSA;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import io.javaoperatorsdk.operator.processing.event.source.filter.OnDeleteFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.OnUpdateFilter;

import static io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig.DEFAULT_CREATE_RESOURCE_ONLY_IF_NOT_EXISTING_WITH_SSA;

public class KubernetesDependentConverter<R extends HasMetadata, P extends HasMetadata> implements
ConfigurationConverter<KubernetesDependent, KubernetesDependentResourceConfig<R>, KubernetesDependentResource<R, P>> {

Expand All @@ -25,6 +27,8 @@ public KubernetesDependentResourceConfig<R> configFrom(KubernetesDependent confi
var namespaces = parentConfiguration.getNamespaces();
var configuredNS = false;
String labelSelector = null;
var createResourceOnlyIfNotExistingWithSSA =
DEFAULT_CREATE_RESOURCE_ONLY_IF_NOT_EXISTING_WITH_SSA;
OnAddFilter<? extends HasMetadata> onAddFilter = null;
OnUpdateFilter<? extends HasMetadata> onUpdateFilter = null;
OnDeleteFilter<? extends HasMetadata> onDeleteFilter = null;
Expand All @@ -39,9 +43,8 @@ public KubernetesDependentResourceConfig<R> configFrom(KubernetesDependent confi
final var fromAnnotation = configAnnotation.labelSelector();
labelSelector = Constants.NO_VALUE_SET.equals(fromAnnotation) ? null : fromAnnotation;

final var context =
Utils.contextFor(parentConfiguration, originatingClass,
configAnnotation.annotationType());
final var context = Utils.contextFor(parentConfiguration, originatingClass,
configAnnotation.annotationType());
onAddFilter = Utils.instantiate(configAnnotation.onAddFilter(), OnAddFilter.class, context);
onUpdateFilter =
Utils.instantiate(configAnnotation.onUpdateFilter(), OnUpdateFilter.class, context);
Expand All @@ -53,9 +56,12 @@ public KubernetesDependentResourceConfig<R> configFrom(KubernetesDependent confi
resourceDiscriminator =
Utils.instantiate(configAnnotation.resourceDiscriminator(), ResourceDiscriminator.class,
context);
createResourceOnlyIfNotExistingWithSSA =
configAnnotation.createResourceOnlyIfNotExistingWithSSA();
}

return new KubernetesDependentResourceConfig(namespaces, labelSelector, configuredNS,
createResourceOnlyIfNotExistingWithSSA,
resourceDiscriminator, onAddFilter, onUpdateFilter, onDeleteFilter, genericFilter);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,12 @@ protected R handleUpdate(R actual, R desired, P primary, Context<P> context) {
public R create(R target, P primary, Context<P> context) {
if (useSSA(context)) {
// setting resource version for SSA so only created if it doesn't exist already
target.getMetadata().setResourceVersion("1");
var createIfNotExisting = kubernetesDependentResourceConfig == null
? KubernetesDependentResourceConfig.DEFAULT_CREATE_RESOURCE_ONLY_IF_NOT_EXISTING_WITH_SSA
: kubernetesDependentResourceConfig.createResourceOnlyIfNotExistingWithSSA();
if (createIfNotExisting) {
target.getMetadata().setResourceVersion("1");
}
}
final var resource = prepare(target, primary, "Creating");
return useSSA(context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@

public class KubernetesDependentResourceConfig<R> {

public static final boolean DEFAULT_CREATE_RESOURCE_ONLY_IF_NOT_EXISTING_WITH_SSA = true;

private Set<String> namespaces = Constants.SAME_AS_CONTROLLER_NAMESPACES_SET;
private String labelSelector = NO_VALUE_SET;
private boolean namespacesWereConfigured = false;
private boolean createResourceOnlyIfNotExistingWithSSA;
private ResourceDiscriminator<R, ?> resourceDiscriminator;

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

public KubernetesDependentResourceConfig() {}

public KubernetesDependentResourceConfig(Set<String> namespaces, String labelSelector,
boolean configuredNS, ResourceDiscriminator<R, ?> resourceDiscriminator,
public KubernetesDependentResourceConfig(Set<String> namespaces,
String labelSelector,
boolean configuredNS,
boolean createResourceOnlyIfNotExistingWithSSA,
ResourceDiscriminator<R, ?> resourceDiscriminator,
OnAddFilter<R> onAddFilter,
OnUpdateFilter<R> onUpdateFilter,
OnDeleteFilter<R> onDeleteFilter, GenericFilter<R> genericFilter) {
this.namespaces = namespaces;
this.labelSelector = labelSelector;
this.namespacesWereConfigured = configuredNS;
this.createResourceOnlyIfNotExistingWithSSA = createResourceOnlyIfNotExistingWithSSA;
this.onAddFilter = onAddFilter;
this.onUpdateFilter = onUpdateFilter;
this.onDeleteFilter = onDeleteFilter;
Expand All @@ -44,7 +51,8 @@ public KubernetesDependentResourceConfig(Set<String> namespaces, String labelSel
}

public KubernetesDependentResourceConfig(Set<String> namespaces, String labelSelector) {
this(namespaces, labelSelector, true, null, null, null,
this(namespaces, labelSelector, true, DEFAULT_CREATE_RESOURCE_ONLY_IF_NOT_EXISTING_WITH_SSA,
null, null, null,
null, null);
}

Expand All @@ -70,6 +78,9 @@ public OnAddFilter onAddFilter() {
return onAddFilter;
}

public boolean createResourceOnlyIfNotExistingWithSSA() {
return createResourceOnlyIfNotExistingWithSSA;
}

public OnUpdateFilter<R> onUpdateFilter() {
return onUpdateFilter;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package io.javaoperatorsdk.operator;

import java.time.Duration;
import java.util.Map;

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.ConfigMapBuilder;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
import io.javaoperatorsdk.operator.sample.createonlyifnotexistsdependentwithssa.CreateOnlyIfNotExistingDependentWithSSACustomResource;
import io.javaoperatorsdk.operator.sample.createonlyifnotexistsdependentwithssa.CreateOnlyIfNotExistingDependentWithSSAReconciler;

import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;

class CreateOnlyIfNotExistingDependentWithSSA {

public static final String TEST_RESOURCE_NAME = "test1";
public static final String KEY = "key";

@RegisterExtension
LocallyRunOperatorExtension extension =
LocallyRunOperatorExtension.builder()
.withReconciler(new CreateOnlyIfNotExistingDependentWithSSAReconciler())
.build();


@Test
void createsResourceOnlyIfNotExisting() {
var cm = new ConfigMapBuilder().withMetadata(new ObjectMetaBuilder()
.withName(TEST_RESOURCE_NAME)
.build())
.withData(Map.of(KEY, "val"))
.build();

extension.create(cm);
extension.create(testResource());

await().pollDelay(Duration.ofMillis(200)).untilAsserted(() -> {
var currentCM = extension.get(ConfigMap.class, TEST_RESOURCE_NAME);
assertThat(currentCM.getData()).containsKey(KEY);
});
}

CreateOnlyIfNotExistingDependentWithSSACustomResource testResource() {
var res = new CreateOnlyIfNotExistingDependentWithSSACustomResource();
res.setMetadata(new ObjectMetaBuilder()
.withName(TEST_RESOURCE_NAME)
.build());

return res;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.javaoperatorsdk.operator.sample.createonlyifnotexistsdependentwithssa;

import java.util.Map;

import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;

public class ConfigMapDependentResource extends
CRUDKubernetesDependentResource<ConfigMap, CreateOnlyIfNotExistingDependentWithSSACustomResource> {

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

@Override
protected ConfigMap desired(CreateOnlyIfNotExistingDependentWithSSACustomResource primary,
Context<CreateOnlyIfNotExistingDependentWithSSACustomResource> context) {
ConfigMap configMap = new ConfigMap();
configMap.setMetadata(new ObjectMetaBuilder()
.withName(primary.getMetadata().getName())
.withNamespace(primary.getMetadata().getNamespace())
.build());
configMap.setData(Map.of("drkey", "v"));
return configMap;
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.javaoperatorsdk.operator.sample.createonlyifnotexistsdependentwithssa;

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 CreateOnlyIfNotExistingDependentWithSSACustomResource
extends CustomResource<Void, Void>
implements Namespaced {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.javaoperatorsdk.operator.sample.createonlyifnotexistsdependentwithssa;

import java.util.concurrent.atomic.AtomicInteger;

import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;

@ControllerConfiguration(dependents = {
@Dependent(type = ConfigMapDependentResource.class)})
public class CreateOnlyIfNotExistingDependentWithSSAReconciler
implements Reconciler<CreateOnlyIfNotExistingDependentWithSSACustomResource> {

private final AtomicInteger numberOfExecutions = new AtomicInteger(0);

@Override
public UpdateControl<CreateOnlyIfNotExistingDependentWithSSACustomResource> reconcile(
CreateOnlyIfNotExistingDependentWithSSACustomResource resource,
Context<CreateOnlyIfNotExistingDependentWithSSACustomResource> context) {
numberOfExecutions.addAndGet(1);
return UpdateControl.noUpdate();
}

public int getNumberOfExecutions() {
return numberOfExecutions.get();
}



}

0 comments on commit 0d22160

Please sign in to comment.