Skip to content
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

feat: create resource only if not exists #2001

Merged
merged 3 commits into from
Aug 8, 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 @@ -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();
}



}
Loading